From 3cf5bcfe49c74ba429b87e04cf652833e7e1b260 Mon Sep 17 00:00:00 2001 From: Someone Serge Date: Sat, 11 Nov 2023 19:43:04 +0000 Subject: [PATCH] nix-required-mounts: restore the followSymlinks option This way pkgs.nix-required-mounts is "correct" even before we override it in the NixOS module --- .../modules/programs/nix-required-mounts.nix | 22 +++++-- .../nix_required_mounts.py | 57 ++++++++++++++----- .../ni/nix-required-mounts/package.nix | 4 +- 3 files changed, 59 insertions(+), 24 deletions(-) diff --git a/nixos/modules/programs/nix-required-mounts.nix b/nixos/modules/programs/nix-required-mounts.nix index 611c065815e1..1c79e532a036 100644 --- a/nixos/modules/programs/nix-required-mounts.nix +++ b/nixos/modules/programs/nix-required-mounts.nix @@ -5,13 +5,17 @@ let package = pkgs.nix-required-mounts; overridenPackage = package.override { inherit (cfg) allowedPatterns; }; - Mount = with lib; types.submodule { - options.host = mkOption { type = types.str; description = "Host path to mount"; }; - options.guest = mkOption { - type = types.str; - description = "Location in the sandbox to mount the host path at"; + Mount = with lib; + types.submodule { + options.host = mkOption { + type = types.str; + description = "Host path to mount"; + }; + options.guest = mkOption { + type = types.str; + description = "Location in the sandbox to mount the host path at"; + }; }; - }; Pattern = with lib.types; types.submodule ({ config, name, ... }: { options.onFeatures = lib.mkOption { @@ -25,6 +29,11 @@ let description = "A list of glob patterns, indicating which paths to expose to the sandbox"; }; + options.unsafeFollowSymlinks = lib.mkEnableOption '' + Instructs the hook to mount the symlink targets as well, when any of + the `paths` contain symlinks. This may not work correctly with glob + patterns. + ''; }); driverPaths = [ @@ -40,6 +49,7 @@ let defaults = { nvidia-gpu.onFeatures = package.allowedPatterns.nvidia-gpu.onFeatures; nvidia-gpu.paths = package.allowedPatterns.nvidia-gpu.paths ++ driverPaths; + nvidia-gpu.unsafeFollowSymlinks = false; }; in { diff --git a/pkgs/by-name/ni/nix-required-mounts/nix_required_mounts.py b/pkgs/by-name/ni/nix-required-mounts/nix_required_mounts.py index 5edf61ff115b..50f0f80f0bf2 100644 --- a/pkgs/by-name/ni/nix-required-mounts/nix_required_mounts.py +++ b/pkgs/by-name/ni/nix-required-mounts/nix_required_mounts.py @@ -5,10 +5,11 @@ import json import subprocess import textwrap from argparse import ArgumentParser +from collections import deque from itertools import chain from pathlib import Path from sys import stderr -from typing import Dict, List, Tuple, TypeAlias, TypedDict +from typing import Deque, Dict, List, Set, Tuple, TypeAlias, TypedDict Glob: TypeAlias = str PathString: TypeAlias = str @@ -22,6 +23,7 @@ class Mount(TypedDict): class Pattern(TypedDict): onFeatures: List[str] paths: List[Glob | Mount] + unsafeFollowSymlinks: bool class HookConfig(TypedDict): @@ -50,7 +52,11 @@ parser.add_argument( def symlink_parents(p: Path) -> List[Path]: out = [] while p.is_symlink() and p not in out: - p = p.readlink() + parent = p.readlink() + if parent.is_relative_to("."): + p = p / parent + else: + p = parent out.append(p) return out @@ -111,38 +117,59 @@ def entrypoint(): parsed_drv = parsed_drv[canon_drv_path] drv_env = parsed_drv.get("env", {}) - features = get_strings(drv_env, "requiredSystemFeatures") - features = list(filter(known_features.__contains__, features)) + required_features = get_strings(drv_env, "requiredSystemFeatures") + required_features = list(filter(known_features.__contains__, required_features)) - patterns: List[PathString | Mount] = list( - chain.from_iterable(allowed_patterns[f]["paths"] for f in features) + patterns: List[Tuple[PathString | Mount, bool]] = list( + (path, pattern["unsafeFollowSymlinks"]) + for pattern in allowed_patterns.values() + for path in pattern["paths"] + if any(feature in required_features for feature in pattern["onFeatures"]) ) # noqa: E501 - # TODO: Would it make sense to preserve the original order instead? - roots: List[Tuple[PathString, PathString]] = sorted( - set( + queue: Deque[Tuple[PathString, PathString, bool]] = deque( + ( mnt - for pattern in patterns + for (pattern, follow_symlinks) in patterns for mnt in ( - ((path, path) for path in glob.glob(pattern)) + ((path, path, follow_symlinks) for path in glob.glob(pattern)) if isinstance(pattern, PathString) - else [(pattern["guest"], pattern["host"])] + else [(pattern["guest"], pattern["host"], follow_symlinks)] ) ) ) + unique_mounts: Set[Tuple[PathString, PathString]] = set() + mounts: List[Tuple[PathString, PathString]] = [] + + while queue: + guest_path, host_path, follow_symlinks = queue.popleft() + if (guest_path, host_path) not in unique_mounts: + mounts.append((guest_path, host_path)) + unique_mounts.add((guest_path, host_path)) + + if not follow_symlinks: + continue + + for parent in symlink_parents(Path(host_path)): + parent_str = parent.absolute().as_posix() + queue.append((parent_str, parent_str, follow_symlinks)) + # the pre-build-hook command if args.issue_command == "always" or ( - args.issue_command == "conditional" and roots + args.issue_command == "conditional" and mounts ): print("extra-sandbox-paths") + print_paths = True + else: + print_paths = False # arguments, one per line - for guest_path, host_path in roots: + for guest_path, host_path in mounts if print_paths else []: print(f"{guest_path}={host_path}") # terminated by an empty line - something_to_terminate = args.issue_stop == "conditional" and roots + something_to_terminate = args.issue_stop == "conditional" and mounts if args.issue_stop == "always" or something_to_terminate: print() diff --git a/pkgs/by-name/ni/nix-required-mounts/package.nix b/pkgs/by-name/ni/nix-required-mounts/package.nix index a17962183de1..8e2611552d05 100644 --- a/pkgs/by-name/ni/nix-required-mounts/package.nix +++ b/pkgs/by-name/ni/nix-required-mounts/package.nix @@ -6,14 +6,12 @@ nvidia-gpu.onFeatures = [ "gpu" "nvidia-gpu" "opengl" "cuda" ]; # It exposes these paths in the sandbox: nvidia-gpu.paths = [ - # Note that mounting /run/opengl-driver/lib actually isn't sufficient, - # because it's populated with symlinks. One most also mount their - # targets, which is what the NixOS module additionaly does. addOpenGLRunpath.driverLink "/dev/dri" "/dev/nvidia*" "/dev/video*" ]; + nvidia-gpu.unsafeFollowSymlinks = true; } , buildPackages , formats