diff --git a/nixos/tests/systemd-journal.nix b/nixos/tests/systemd-journal.nix index c50c151ae10..b0a3f62ce1c 100644 --- a/nixos/tests/systemd-journal.nix +++ b/nixos/tests/systemd-journal.nix @@ -13,6 +13,8 @@ import ./make-test-python.nix ({ pkgs, ... }: testScript = '' machine.wait_for_unit("multi-user.target") + machine.succeed("journalctl --grep=systemd") + machine.succeed( "${pkgs.curl}/bin/curl -s localhost:19531/machine | ${pkgs.jq}/bin/jq -e '.hostname == \"machine\"'" ) diff --git a/pkgs/os-specific/linux/systemd/default.nix b/pkgs/os-specific/linux/systemd/default.nix index e6cb589c9bf..2822bffdb51 100644 --- a/pkgs/os-specific/linux/systemd/default.nix +++ b/pkgs/os-specific/linux/systemd/default.nix @@ -160,6 +160,90 @@ stdenv.mkDerivation { --replace \ "find_program('objcopy'" \ "find_program('${stdenv.cc.bintools.targetPrefix}objcopy'" + '' + (let + + # The folllowing dlopen patches ensure that all the features that are + # implemented via dlopen(3) are available (or explicitly deactivated) by + # pointing dlopen to the absolute store path instead of relying on the + # linkers runtime lookup code. + # + # All of the dlopen calls have to be handled. When new ones are introduced + # by upstream (or one of our patches) they must be explicitly declared, + # otherwise the build will fail. + # + # As of systemd version 247 we've seen a few errors like `libpcre2.… not + # found` when using e.g. --grep with journalctl. Those errors should + # become less unexpected now. + # + # There are generally two classes of dlopen(3) calls. Those that we want to + # support and those that should be deactivated / unsupported. This change + # enforces that we handle all dlopen calls explicitly. Meaning: There is + # not a single dlopen call in the source code tree that we did not + # explicitly handle. + # + # In order to do this we introduced a list of attributes that maps from + # shared object name to the package that contains them. The package can be + # null meaning the reference should be nuked and the shared object will + # never be loadable during runtime (because it points at an invalid store + # path location). + # + # To get a list of dynamically loaded libraries issue something like + # `grep -ri 'dlopen("lib' $src` and update the below list. + dlopenLibs = [ + # We did never provide support for libxkbcommon & qrencode + { name = "libxkbcommon.so.0"; pkg = null; } + { name = "libqrencode.so.4"; pkg = null; } + + # We did not provide libpwquality before so it is safe to disable it for + # now. + { name = "libpwquality.so.1"; pkg = null; } + + # Only include cryptsetup if it is enabled. We might not be able to + # provide it during "bootstrap" in e.g. the minimal systemd build as + # cryptsetup has udev (aka systemd) in it's dependencies. + { name = "libcryptsetup.so.12"; pkg = if withCryptsetup then cryptsetup else null; } + + # We are using libidn2 so we only provide that and ignore the others. + # Systemd does this decision during configure time and uses ifdef's to + # enable specific branches. We can safely ignore (nuke) the libidn "v1" + # libraries. + { name = "libidn2.so.0"; pkg = libidn2; } + { name = "libidn.so.12"; pkg = null; } + { name = "libidn.so.11"; pkg = null; } + + # journalctl --grep requires libpcre so lets provide it + { name = "libpcre2-8.so.0"; pkg = pcre2; } + ]; + + patchDlOpen = dl: let + library = "${lib.makeLibraryPath [dl.pkg]}/${dl.name}"; + in if dl.pkg == null then '' + # remove the dependency on the library by replacing it with an invalid path + for file in $(grep -lr 'dlopen("${dl.name}"' src); do + echo "patching dlopen(\"${dl.name}\", …) in $file to an invalid store path ("/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-not-implemented/${dl.name}")…" + substituteInPlace "$file" --replace 'dlopen("${dl.name}"' 'dlopen("/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-not-implemented/${dl.name}"' + done + '' else '' + # ensure that the library we provide actually exists + if ! [ -e ${library} ]; then + echo 'The shared library `${library}` does not exist but was given as subtitute for `${dl.name}`' + exit 1 + fi + # make the path to the dependency explicit + for file in $(grep -lr 'dlopen("${dl.name}"' src); do + echo "patching dlopen(\"${dl.name}\", …) in $file to ${library}…" + substituteInPlace "$file" --replace 'dlopen("${dl.name}"' 'dlopen("${library}"' + done + ''; + in # patch all the dlopen calls to contain absolute paths to the libraries + lib.concatMapStringsSep "\n" patchDlOpen dlopenLibs) + # finally ensure that there are no left-over dlopen calls that we didn't handle + + '' + if grep -qr 'dlopen("[^/]' src; then + echo "Found unhandled dlopen calls: " + grep -r 'dlopen("[^/]' src + exit 1 + fi ''; outputs = [ "out" "man" "dev" ];