A goal here was to minimize the amount of C that needed to be written, so I designed resolve_in_root as the smallest possible program that could usefully expose RESOLVE_IN_ROOT functionality to scripts in a generic way. Rather than a program that does the resolution and then prints the absolute path, I also considered a program that would chdir() to the resulting directory and then exec a new argv. This was an even simpler implementation, since it didn't require any string handling at all, but it was a bit awkward to use and only worked for directories. resolve_in_root lives in the new tools/ directory rather than in host/, because it will also be useful in guests that need to interact with these Nix profiles. Signed-off-by: Alyssa Ross <hi@alyssa.is> --- host/rootfs/default.nix | 8 ++- host/rootfs/etc/s6-rc/ext-rc-init/up | 8 ++- tools/resolve_in_root/default.nix | 23 ++++++++ tools/resolve_in_root/meson.build | 10 ++++ tools/resolve_in_root/resolve_in_root.c | 76 +++++++++++++++++++++++++ tools/resolve_in_root/test.sh | 11 ++++ 6 files changed, 131 insertions(+), 5 deletions(-) create mode 100644 tools/resolve_in_root/default.nix create mode 100644 tools/resolve_in_root/meson.build create mode 100644 tools/resolve_in_root/resolve_in_root.c create mode 100755 tools/resolve_in_root/test.sh diff --git a/host/rootfs/default.nix b/host/rootfs/default.nix index 0b08603..f5c8bcf 100644 --- a/host/rootfs/default.nix +++ b/host/rootfs/default.nix @@ -14,6 +14,10 @@ let inherit (lib) cleanSource cleanSourceWith concatMapStringsSep hasSuffix; inherit (nixosAllHardware.config.hardware) firmware; + resolve_in_root = import ../../tools/resolve_in_root { + config = config // { pkgs = pkgs.pkgsStatic; }; + }; + start-vm = import ../start-vm { config = config // { pkgs = pkgs.pkgsStatic; }; }; @@ -44,8 +48,8 @@ let foot = pkgsGui.foot.override { allowPgo = false; }; packages = [ - cloud-hypervisor e2fsprogs execline jq kmod mdevd s6 s6-linux-init s6-rc - socat start-vm virtiofsd + cloud-hypervisor e2fsprogs execline jq kmod mdevd resolve_in_root s6 + s6-linux-init s6-rc socat start-vm virtiofsd (cryptsetup.override { programs = { diff --git a/host/rootfs/etc/s6-rc/ext-rc-init/up b/host/rootfs/etc/s6-rc/ext-rc-init/up index 53ab127..4808f28 100644 --- a/host/rootfs/etc/s6-rc/ext-rc-init/up +++ b/host/rootfs/etc/s6-rc/ext-rc-init/up @@ -5,9 +5,11 @@ if { mkdir -p /run/s6-rc.ext.src } if { - elglob -0 dirs /ext/svc/data/*/ - forx -E dir { $dirs } - backtick -E name { basename -- $dir } + cd /ext + elglob -0 glob svc/data/* + forx -E reldir { $glob } + backtick -E name { basename -- $reldir } + backtick -E dir { resolve_in_root . $reldir } cd /run/s6-rc.ext.src diff --git a/tools/resolve_in_root/default.nix b/tools/resolve_in_root/default.nix new file mode 100644 index 0000000..f3ed081 --- /dev/null +++ b/tools/resolve_in_root/default.nix @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2022 Alyssa Ross <hi@alyssa.is> + +{ config ? import ../../nix/eval-config.nix {} }: config.pkgs.callPackage ( +{ lib, stdenv, meson, ninja }: + +let + inherit (lib) cleanSource cleanSourceWith hasSuffix; +in + +stdenv.mkDerivation { + name = "resolve_in_root"; + + src = cleanSourceWith { + filter = name: _type: !(hasSuffix ".nix" name); + src = cleanSource ./.; + }; + + nativeBuildInputs = [ meson ninja ]; + + doCheck = true; +} +) { } diff --git a/tools/resolve_in_root/meson.build b/tools/resolve_in_root/meson.build new file mode 100644 index 0000000..a549ea6 --- /dev/null +++ b/tools/resolve_in_root/meson.build @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: EUPL-1.2+ +# SPDX-FileCopyrightText: 2022 Alyssa Ross <hi@alyssa.is> + +project('resolve_in_root', 'c') + +add_project_arguments('-D_GNU_SOURCE', language : 'c') + +prog = executable('resolve_in_root', 'resolve_in_root.c', install : true) + +test('test', find_program('test.sh'), args : prog) diff --git a/tools/resolve_in_root/resolve_in_root.c b/tools/resolve_in_root/resolve_in_root.c new file mode 100644 index 0000000..ab33c91 --- /dev/null +++ b/tools/resolve_in_root/resolve_in_root.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: EUPL-1.2+ +// SPDX-FileCopyrightText: 2022 Alyssa Ross <hi@alyssa.is> + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include <sys/syscall.h> + +#include <linux/openat2.h> + +#define STRINGIZE(x) STRINGIZE2(x) +#define STRINGIZE2(x) #x + +[[gnu::malloc]] [[gnu::nonnull]] char *areadlink(const char *pathname) +{ + char *target = NULL; + size_t link_size, target_size = 4096; + + for (;;) { + free(target); + if (!(target = malloc(target_size))) + return NULL; + if ((link_size = readlink(pathname, target, target_size)) == -1) + return NULL; + if (link_size < target_size) + break; + if (target_size > (((size_t)-1) >> 1)) { + errno = ENOMEM; + return NULL; + } + target_size <<= 1; + } + target[link_size] = '\0'; + + return target; +} + +int main(int argc, char **argv) +{ + // -1 because both paths include a null terminator. + char fdpath[sizeof "/proc/self/fd/" + sizeof(STRINGIZE(INT_MAX)) - 1]; + + char *target; + int rootfd, targetfd; + struct open_how how = { .flags = O_PATH }; + + // Ensure INT_MAX is actually defined, because otherwise + // sizeof("INT_MAX") will silently be used instead in fdpath's + // size. + (void) INT_MAX; + + if (argc != 3) { + fprintf(stderr, "Usage: %s root dir\n", argc ? argv[0] : "resolve_in_root"); + return 1; + } + + if ((rootfd = syscall(SYS_openat2, AT_FDCWD, argv[1], &how, sizeof how)) == -1) + err(EXIT_FAILURE, "opening %s", argv[1]); + + how.resolve = RESOLVE_IN_ROOT | RESOLVE_NO_MAGICLINKS | RESOLVE_NO_XDEV; + if ((targetfd = syscall(SYS_openat2, rootfd, argv[2], &how, sizeof how)) == -1) + err(EXIT_FAILURE, "opening %s with %s:/", argv[2], argv[1]); + + assert(snprintf(fdpath, sizeof fdpath, "/proc/self/fd/%d", targetfd) < sizeof fdpath); + + target = areadlink(fdpath); + if (!target) + err(EXIT_FAILURE, "reading %s", fdpath); + puts(target); +} diff --git a/tools/resolve_in_root/test.sh b/tools/resolve_in_root/test.sh new file mode 100755 index 0000000..c4afe3d --- /dev/null +++ b/tools/resolve_in_root/test.sh @@ -0,0 +1,11 @@ +#!/bin/sh -ue +# SPDX-License-Identifier: EUPL-1.2+ +# SPDX-FileCopyrightText: 2022 Alyssa Ross <hi@alyssa.is> + +dir="$(mktemp -d)" +trap 'rm -rf "$dir"' EXIT + +touch "$dir/file" +ln -s /file "$dir/link" + +test "$("$1" "$dir" link)" = "$dir/file" -- 2.37.1