aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.clang-tidy3
-rw-r--r--doc/manual/change-authors.yml5
-rw-r--r--doc/manual/rl-next/block-io-uring.md12
-rw-r--r--doc/manual/rl-next/repl-edit-store.md11
-rw-r--r--flake.lock18
-rw-r--r--flake.nix28
-rw-r--r--maintainers/check-syscalls.nix16
-rwxr-xr-xmaintainers/check-syscalls.sh7
-rw-r--r--meson.build20
-rw-r--r--misc/pegtl.nix23
-rw-r--r--misc/pre-commit.nix2
-rw-r--r--nix-support/nixfmt.nix65
-rw-r--r--package.nix73
-rw-r--r--releng/README.md2
-rw-r--r--releng/create_release.xsh19
-rw-r--r--releng/docker.xsh20
-rw-r--r--releng/version.py1
-rw-r--r--src/asan-options/asan-options.cc17
-rw-r--r--src/build-remote/build-remote.cc4
-rw-r--r--src/libcmd/installable-attr-path.cc12
-rw-r--r--src/libcmd/installable-attr-path.hh14
-rw-r--r--src/libcmd/repl.cc9
-rw-r--r--src/libexpr/eval.cc9
-rw-r--r--src/libexpr/flake/flakeref.cc11
-rw-r--r--src/libexpr/gc-alloc.hh4
-rw-r--r--src/libexpr/get-drvs.cc288
-rw-r--r--src/libexpr/get-drvs.hh3
-rw-r--r--src/libexpr/json-to-value.cc28
-rw-r--r--src/libexpr/parser/parser.cc1
-rw-r--r--src/libexpr/value.hh398
-rw-r--r--src/libfetchers/fetchers.hh31
-rw-r--r--src/libfetchers/git.cc19
-rw-r--r--src/libfetchers/github.cc114
-rw-r--r--src/libfetchers/indirect.cc34
-rw-r--r--src/libfetchers/mercurial.cc7
-rw-r--r--src/libfetchers/tarball.cc26
-rw-r--r--src/libstore/build-result.cc23
-rw-r--r--src/libstore/build-result.hh14
-rw-r--r--src/libstore/build/derivation-goal.cc10
-rw-r--r--src/libstore/build/drv-output-substitution-goal.cc11
-rw-r--r--src/libstore/build/drv-output-substitution-goal.hh7
-rw-r--r--src/libstore/build/entry-points.cc9
-rw-r--r--src/libstore/build/goal.cc38
-rw-r--r--src/libstore/build/goal.hh22
-rw-r--r--src/libstore/build/local-derivation-goal.cc559
-rw-r--r--src/libstore/build/substitution-goal.cc31
-rw-r--r--src/libstore/build/substitution-goal.hh5
-rw-r--r--src/libstore/build/worker.cc56
-rw-r--r--src/libstore/build/worker.hh27
-rw-r--r--src/libstore/daemon.cc2
-rw-r--r--src/libstore/globals.cc4
-rw-r--r--src/libstore/linux/fchmodat2-compat.hh35
-rw-r--r--src/libstore/local-store.cc2
-rw-r--r--src/libstore/meson.build35
-rw-r--r--src/libstore/platform.cc10
-rw-r--r--src/libstore/platform/darwin.cc8
-rw-r--r--src/libstore/platform/freebsd.cc142
-rw-r--r--src/libstore/platform/freebsd.hh47
-rw-r--r--src/libutil/concepts.hh22
-rw-r--r--src/libutil/current-process.cc23
-rw-r--r--src/libutil/file-system.cc16
-rw-r--r--src/libutil/logging.cc1
-rw-r--r--src/libutil/meson.build1
-rw-r--r--src/libutil/processes.cc2
-rw-r--r--src/libutil/processes.hh2
-rw-r--r--src/libutil/serialise.hh8
-rw-r--r--src/libutil/tarfile.cc4
-rw-r--r--src/meson.build11
-rw-r--r--src/nix/diff-closures.cc15
-rw-r--r--src/nix/meson.build1
-rw-r--r--src/resolve-system-dependencies/resolve-system-dependencies.cc2
-rw-r--r--tests/functional/fetchers.sh91
-rw-r--r--tests/functional/flakes/subdir-flake.sh20
-rw-r--r--tests/functional/meson.build2
-rw-r--r--tests/functional/repl.sh27
-rw-r--r--tests/functional/repl_characterization/meson.build1
-rw-r--r--tests/functional/test-libstoreconsumer/meson.build1
-rw-r--r--tests/nixos/default.nix2
-rw-r--r--tests/nixos/io_uring/default.nix7
-rw-r--r--tests/nixos/io_uring/package.nix19
-rw-r--r--tests/nixos/setuid/fchmodat2-suid.c5
-rw-r--r--tests/nixos/tarball-flakes.nix2
-rw-r--r--tests/unit/meson.build17
-rw-r--r--version.json1
84 files changed, 2042 insertions, 712 deletions
diff --git a/.clang-tidy b/.clang-tidy
index 3b5dcd91a..0cc1f2520 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -16,3 +16,6 @@ Checks:
- -bugprone-unchecked-optional-access
# many warnings, seems like a questionable lint
- -bugprone-branch-clone
+
+CheckOptions:
+ bugprone-reserved-identifier.AllowedIdentifiers: '__asan_default_options'
diff --git a/doc/manual/change-authors.yml b/doc/manual/change-authors.yml
index 49df7f5df..630af29ff 100644
--- a/doc/manual/change-authors.yml
+++ b/doc/manual/change-authors.yml
@@ -57,6 +57,11 @@ ericson:
display_name: John Ericson
github: ericson2314
+goldstein:
+ display_name: goldstein
+ forgejo: goldstein
+ github: GoldsteinE
+
horrors:
display_name: eldritch horrors
forgejo: pennae
diff --git a/doc/manual/rl-next/block-io-uring.md b/doc/manual/rl-next/block-io-uring.md
new file mode 100644
index 000000000..6ebba9a20
--- /dev/null
+++ b/doc/manual/rl-next/block-io-uring.md
@@ -0,0 +1,12 @@
+---
+synopsis: "Block io_uring in the Linux sandbox"
+cls: 1611
+credits: alois31
+category: Breaking Changes
+---
+
+The io\_uring API has the unfortunate property that it is not possible to selectively decide which operations should be allowed.
+This, together with the fact that new operations are routinely added, makes it a hazard to the proper function of the sandbox.
+
+Therefore, any access to io\_uring has been made unavailable inside the sandbox.
+As such, attempts to execute any system calls forming part of this API will fail with the error `ENOSYS`, as if io\_uring support had not been configured into the kernel.
diff --git a/doc/manual/rl-next/repl-edit-store.md b/doc/manual/rl-next/repl-edit-store.md
new file mode 100644
index 000000000..e93bde07d
--- /dev/null
+++ b/doc/manual/rl-next/repl-edit-store.md
@@ -0,0 +1,11 @@
+---
+synopsis: "`:edit`ing a file in Nix store no longer reloads the repl"
+issues: [fj#341]
+cls: [1620]
+category: Improvements
+credits: [goldstein]
+---
+
+Calling `:edit` from the repl now only reloads if the file being edited was outside of Nix store.
+That means that all the local variables are now preserved across `:edit`s of store paths.
+This is always safe because the store is read-only.
diff --git a/flake.lock b/flake.lock
index a3306f941..84e578bc3 100644
--- a/flake.lock
+++ b/flake.lock
@@ -19,11 +19,11 @@
"nix2container": {
"flake": false,
"locked": {
- "lastModified": 1712990762,
- "narHash": "sha256-hO9W3w7NcnYeX8u8cleHiSpK2YJo7ecarFTUlbybl7k=",
+ "lastModified": 1720642556,
+ "narHash": "sha256-qsnqk13UmREKmRT7c8hEnz26X3GFFyIQrqx4EaRc1Is=",
"owner": "nlewo",
"repo": "nix2container",
- "rev": "20aad300c925639d5d6cbe30013c8357ce9f2a2e",
+ "rev": "3853e5caf9ad24103b13aa6e0e8bcebb47649fe4",
"type": "github"
},
"original": {
@@ -34,11 +34,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1718111384,
- "narHash": "sha256-7tSst0S5FOmcgvNtfy6cjZX5w8CabCVAfAeCkhY4OVg=",
+ "lastModified": 1721931987,
+ "narHash": "sha256-1Zg8LY0T5EfXtv0Kf4M6SFnjH7Eto4VV+EKJ/YSnhiI=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "a508a44af0c1b1b57785c34d8b54783536273eeb",
+ "rev": "e21630230c77140bc6478a21cd71e8bb73706fce",
"type": "github"
},
"original": {
@@ -67,11 +67,11 @@
"pre-commit-hooks": {
"flake": false,
"locked": {
- "lastModified": 1712055707,
- "narHash": "sha256-4XLvuSIDZJGS17xEwSrNuJLL7UjDYKGJSbK1WWX2AK8=",
+ "lastModified": 1721042469,
+ "narHash": "sha256-6FPUl7HVtvRHCCBQne7Ylp4p+dpP3P/OYuzjztZ4s70=",
"owner": "cachix",
"repo": "git-hooks.nix",
- "rev": "e35aed5fda3cc79f88ed7f1795021e559582093a",
+ "rev": "f451c19376071a90d8c58ab1a953c6e9840527fd",
"type": "github"
},
"original": {
diff --git a/flake.nix b/flake.nix
index 43fba5e3a..a1fc947b7 100644
--- a/flake.nix
+++ b/flake.nix
@@ -33,7 +33,7 @@
# This notice gets echoed as a dev shell hook, and can be turned off with
# `touch .nocontribmsg`
- sgr = ''['';
+ sgr = builtins.fromJSON ''"\u001b["'';
freezePage = "https://wiki.lix.systems/books/lix-contributors/page/freezes-and-recommended-contributions";
codebaseOverview = "https://wiki.lix.systems/books/lix-contributors/page/codebase-overview";
contribNotice = builtins.toFile "lix-contrib-notice" ''
@@ -59,7 +59,8 @@
(Run `touch .nocontribmsg` to hide this message.)
'';
- officialRelease = false;
+ versionJson = builtins.fromJSON (builtins.readFile ./version.json);
+ officialRelease = versionJson.official_release;
# Set to true to build the release notes for the next release.
buildUnreleasedNotes = true;
@@ -140,10 +141,7 @@
system = crossSystem;
}
// lib.optionalAttrs (crossSystem == "x86_64-freebsd") { useLLVM = true; };
- overlays = [
- (overlayFor (p: p.${stdenv}))
- (final: prev: { nixfmt = final.callPackage ./nix-support/nixfmt.nix { }; })
- ];
+ overlays = [ (overlayFor (p: p.${stdenv})) ];
};
stdenvs = forAllStdenvs (make-pkgs null);
native = stdenvs.stdenvPackages;
@@ -167,6 +165,7 @@
nixUnstable = prev.nixUnstable;
check-headers = final.buildPackages.callPackage ./maintainers/check-headers.nix { };
+ check-syscalls = final.buildPackages.callPackage ./maintainers/check-syscalls.nix { };
default-busybox-sandbox-shell = final.busybox.override {
useMusl = true;
@@ -198,8 +197,6 @@
busybox-sandbox-shell = final.busybox-sandbox-shell or final.default-busybox-sandbox-shell;
};
- pegtl = final.nix.passthru.pegtl;
-
# Export the patched version of boehmgc that Lix uses into the overlay
# for consumers of this flake.
boehmgc-nix = final.nix.passthru.boehmgc-nix;
@@ -279,6 +276,19 @@
# System tests.
tests = import ./tests/nixos { inherit lib nixpkgs nixpkgsFor; } // {
+ # This is x86_64-linux only, just because we have significantly
+ # cheaper x86_64-linux compute in CI.
+ # It is clangStdenv because clang's sanitizers are nicer.
+ asanBuild = self.packages.x86_64-linux.nix-clangStdenv.override {
+ sanitize = [
+ "address"
+ "undefined"
+ ];
+ # it is very hard to make *every* CI build use this option such
+ # that we don't wind up building Lix twice, so we do it here where
+ # we are already doing so.
+ werror = true;
+ };
# Make sure that nix-env still produces the exact same result
# on a particular version of Nixpkgs.
@@ -410,7 +420,7 @@
pkgs: stdenv:
let
nix = pkgs.callPackage ./package.nix {
- inherit stdenv officialRelease versionSuffix;
+ inherit stdenv versionSuffix;
busybox-sandbox-shell = pkgs.busybox-sandbox-shell or pkgs.default-busybox-sandbox;
internalApiDocs = false;
};
diff --git a/maintainers/check-syscalls.nix b/maintainers/check-syscalls.nix
new file mode 100644
index 000000000..1a3de5c6d
--- /dev/null
+++ b/maintainers/check-syscalls.nix
@@ -0,0 +1,16 @@
+{
+ runCommandNoCC,
+ lib,
+ libseccomp,
+ writeShellScriptBin,
+}:
+let
+ syscalls-csv = runCommandNoCC "syscalls.csv" { } ''
+ echo ${lib.escapeShellArg libseccomp.src}
+ tar -xf ${lib.escapeShellArg libseccomp.src} --strip-components=2 ${libseccomp.name}/src/syscalls.csv
+ mv syscalls.csv "$out"
+ '';
+in
+writeShellScriptBin "check-syscalls" ''
+ ${./check-syscalls.sh} ${syscalls-csv}
+''
diff --git a/maintainers/check-syscalls.sh b/maintainers/check-syscalls.sh
new file mode 100755
index 000000000..cd72ac23b
--- /dev/null
+++ b/maintainers/check-syscalls.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+set -e
+
+diff -u <(awk < src/libstore/build/local-derivation-goal.cc '/BEGIN extract-syscalls/ { extracting = 1; next }
+match($0, /allowSyscall\(ctx, SCMP_SYS\(([^)]*)\)\);|\/\/ skip ([^ ]*)/, result) { print result[1] result[2] }
+/END extract-syscalls/ { extracting = 0; next }') <(tail -n+2 "$1" | cut -d, -f 1)
diff --git a/meson.build b/meson.build
index 8ce06800b..ed50dff78 100644
--- a/meson.build
+++ b/meson.build
@@ -199,7 +199,11 @@ configdata = { }
# Dependencies
#
-boehm = dependency('bdw-gc', required : get_option('gc'), version : '>=8.2.6')
+gc_opt = get_option('gc').disable_if(
+ 'address' in get_option('b_sanitize'),
+ error_message: 'gc does far too many memory crimes for ASan'
+)
+boehm = dependency('bdw-gc', required : gc_opt, version : '>=8.2.6')
configdata += {
'HAVE_BOEHMGC': boehm.found().to_int(),
}
@@ -314,6 +318,10 @@ nlohmann_json = dependency('nlohmann_json', required : true)
# *absolutely* are not going to make it work)
lix_doc = declare_dependency(link_args : [ '-llix_doc' ])
+if is_freebsd
+ libprocstat = declare_dependency(link_args : [ '-lprocstat' ])
+endif
+
#
# Build-time tools
#
@@ -445,6 +453,7 @@ add_project_arguments(
'-Werror=unused-result',
'-Wdeprecated-copy',
'-Wignored-qualifiers',
+ '-Werror=suggest-override',
# Enable assertions in libstdc++ by default. Harmless on libc++. Benchmarked
# at ~1% overhead in `nix search`.
#
@@ -477,7 +486,14 @@ if cxx.get_id() == 'clang' and get_option('b_sanitize') != ''
add_project_link_arguments('-shared-libsan', language : 'cpp')
endif
+# Clang gets grumpy about missing libasan symbols if -shared-libasan is not
+# passed when building shared libs, at least on Linux
+if cxx.get_id() == 'clang' and 'address' in get_option('b_sanitize')
+ add_project_link_arguments('-shared-libasan', language : 'cpp')
+endif
+
add_project_link_arguments('-pthread', language : 'cpp')
+
if cxx.get_linker_id() in ['ld.bfd', 'ld.gold']
add_project_link_arguments('-Wl,--no-copy-dt-needed-entries', language : 'cpp')
endif
@@ -492,7 +508,7 @@ endif
# maintainers/buildtime_report.sh BUILD-DIR to simply work in clang builds.
#
# They can also be manually viewed at https://ui.perfetto.dev
-if get_option('profile-build').require(meson.get_compiler('cpp').get_id() == 'clang').enabled()
+if get_option('profile-build').require(cxx.get_id() == 'clang').enabled()
add_project_arguments('-ftime-trace', language: 'cpp')
endif
diff --git a/misc/pegtl.nix b/misc/pegtl.nix
deleted file mode 100644
index 3fd999d9d..000000000
--- a/misc/pegtl.nix
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- stdenv,
- cmake,
- ninja,
- fetchFromGitHub,
-}:
-
-stdenv.mkDerivation {
- pname = "pegtl";
- version = "3.2.7";
-
- src = fetchFromGitHub {
- repo = "PEGTL";
- owner = "taocpp";
- rev = "refs/tags/3.2.7";
- hash = "sha256-IV5YNGE4EWVrmg2Sia/rcU8jCuiBynQGJM6n3DCWTQU=";
- };
-
- nativeBuildInputs = [
- cmake
- ninja
- ];
-}
diff --git a/misc/pre-commit.nix b/misc/pre-commit.nix
index 292940e83..59ad2f874 100644
--- a/misc/pre-commit.nix
+++ b/misc/pre-commit.nix
@@ -106,7 +106,7 @@ pre-commit-run {
};
treefmt = {
enable = true;
- settings.formatters = [ pkgs.nixfmt ];
+ settings.formatters = [ pkgs.nixfmt-rfc-style ];
};
};
}
diff --git a/nix-support/nixfmt.nix b/nix-support/nixfmt.nix
deleted file mode 100644
index b51e26295..000000000
--- a/nix-support/nixfmt.nix
+++ /dev/null
@@ -1,65 +0,0 @@
-# Copy of `nixfmt-rfc-style` vendored from `nixpkgs` master:
-# https://github.com/NixOS/nixpkgs/blob/ab6071eb54cc9b66dda436111d4f569e4e56cbf4/pkgs/by-name/ni/nixfmt-rfc-style/package.nix
-{
- haskell,
- haskellPackages,
- fetchFromGitHub,
-}:
-let
- inherit (haskell.lib.compose) justStaticExecutables;
- raw-pkg = haskellPackages.callPackage (
- {
- mkDerivation,
- base,
- cmdargs,
- directory,
- fetchzip,
- filepath,
- lib,
- megaparsec,
- mtl,
- parser-combinators,
- safe-exceptions,
- scientific,
- text,
- transformers,
- unix,
- }:
- mkDerivation {
- pname = "nixfmt";
- version = "0.6.0-unstable-2024-03-14";
- src = fetchFromGitHub {
- owner = "serokell";
- repo = "nixfmt";
- rev = "8d13b593fa8d8d6e5075f541f3231222a08e84df";
- hash = "sha256-HtXvzmfN4wk45qiKZ7V+/5WBV7jnTHfd7iBwF4XGl64=";
- };
- isLibrary = true;
- isExecutable = true;
- libraryHaskellDepends = [
- base
- megaparsec
- mtl
- parser-combinators
- scientific
- text
- transformers
- ];
- executableHaskellDepends = [
- base
- cmdargs
- directory
- filepath
- safe-exceptions
- text
- unix
- ];
- jailbreak = true;
- homepage = "https://github.com/serokell/nixfmt";
- description = "An opinionated formatter for Nix";
- license = lib.licenses.mpl20;
- mainProgram = "nixfmt";
- }
- ) { };
-in
-justStaticExecutables raw-pkg
diff --git a/package.nix b/package.nix
index b9ef0273c..62927e569 100644
--- a/package.nix
+++ b/package.nix
@@ -36,7 +36,7 @@
meson,
ninja,
openssl,
- pegtl ? __forDefaults.pegtl,
+ pegtl,
pkg-config,
python3,
rapidcheck,
@@ -53,16 +53,24 @@
pname ? "lix",
versionSuffix ? "",
- officialRelease ? false,
+ officialRelease ? __forDefaults.versionJson.official_release,
# Set to true to build the release notes for the next release.
buildUnreleasedNotes ? true,
internalApiDocs ? false,
+ # List of Meson sanitize options. Accepts values of b_sanitize, e.g.
+ # "address", "undefined", "thread".
+ sanitize ? null,
+ # Turn compiler warnings into errors.
+ werror ? false,
+
# Not a real argument, just the only way to approximate let-binding some
# stuff for argument defaults.
__forDefaults ? {
canRunInstalled = stdenv.buildPlatform.canExecute stdenv.hostPlatform;
+ versionJson = builtins.fromJSON (builtins.readFile ./version.json);
+
boehmgc-nix = boehmgc.override { enableLargeConfig = true; };
editline-lix = editline.overrideAttrs (prev: {
@@ -71,8 +79,6 @@
lix-doc = callPackage ./lix-doc/package.nix { };
build-release-notes = callPackage ./maintainers/build-release-notes.nix { };
-
- pegtl = callPackage ./misc/pegtl.nix { };
},
}:
let
@@ -80,8 +86,7 @@ let
inherit (lib) fileset;
inherit (stdenv) hostPlatform buildPlatform;
- versionJson = builtins.fromJSON (builtins.readFile ./version.json);
- version = versionJson.version + versionSuffix;
+ version = __forDefaults.versionJson.version + versionSuffix;
aws-sdk-cpp-nix = aws-sdk-cpp.override {
apis = [
@@ -93,31 +98,19 @@ let
# Reimplementation of Nixpkgs' Meson cross file, with some additions to make
# it actually work.
- mesonCrossFile =
- let
- cpuFamily =
- platform:
- with platform;
- if isAarch32 then
- "arm"
- else if isx86_32 then
- "x86"
- else
- platform.uname.processor;
- in
- builtins.toFile "lix-cross-file.conf" ''
- [properties]
- # Meson is convinced that if !buildPlatform.canExecute hostPlatform then we cannot
- # build anything at all, which is not at all correct. If we can't execute the host
- # platform, we'll just disable tests and doc gen.
- needs_exe_wrapper = false
-
- [binaries]
- # Meson refuses to consider any CMake binary during cross compilation if it's
- # not explicitly specified here, in the cross file.
- # https://github.com/mesonbuild/meson/blob/0ed78cf6fa6d87c0738f67ae43525e661b50a8a2/mesonbuild/cmake/executor.py#L72
- cmake = 'cmake'
- '';
+ mesonCrossFile = builtins.toFile "lix-cross-file.conf" ''
+ [properties]
+ # Meson is convinced that if !buildPlatform.canExecute hostPlatform then we cannot
+ # build anything at all, which is not at all correct. If we can't execute the host
+ # platform, we'll just disable tests and doc gen.
+ needs_exe_wrapper = false
+
+ [binaries]
+ # Meson refuses to consider any CMake binary during cross compilation if it's
+ # not explicitly specified here, in the cross file.
+ # https://github.com/mesonbuild/meson/blob/0ed78cf6fa6d87c0738f67ae43525e661b50a8a2/mesonbuild/cmake/executor.py#L72
+ cmake = 'cmake'
+ '';
# The internal API docs need these for the build, but if we're not building
# Nix itself, then these don't need to be propagated.
@@ -181,6 +174,12 @@ stdenv.mkDerivation (finalAttrs: {
dontBuild = false;
mesonFlags =
+ let
+ sanitizeOpts = lib.optionals (sanitize != null) (
+ [ "-Db_sanitize=${builtins.concatStringsSep "," sanitize}" ]
+ ++ lib.optional (builtins.elem "address" sanitize) "-Dgc=disabled"
+ );
+ in
lib.optionals hostPlatform.isLinux [
# You'd think meson could just find this in PATH, but busybox is in buildInputs,
# which don't actually get added to PATH. And buildInputs is correct over
@@ -196,8 +195,10 @@ stdenv.mkDerivation (finalAttrs: {
(lib.mesonEnable "internal-api-docs" internalApiDocs)
(lib.mesonBool "enable-tests" finalAttrs.finalPackage.doCheck)
(lib.mesonBool "enable-docs" canRunInstalled)
+ (lib.mesonBool "werror" werror)
]
- ++ lib.optional (hostPlatform != buildPlatform) "--cross-file=${mesonCrossFile}";
+ ++ lib.optional (hostPlatform != buildPlatform) "--cross-file=${mesonCrossFile}"
+ ++ sanitizeOpts;
# We only include CMake so that Meson can locate toml11, which only ships CMake dependency metadata.
dontUseCmakeConfigure = true;
@@ -384,8 +385,6 @@ stdenv.mkDerivation (finalAttrs: {
pegtl
;
- inherit officialRelease;
-
# The collection of dependency logic for this derivation is complicated enough that
# it's easier to parameterize the devShell off an already called package.nix.
mkDevShell =
@@ -399,13 +398,14 @@ stdenv.mkDerivation (finalAttrs: {
glibcLocales,
just,
llvmPackages,
- nixfmt,
+ nixfmt-rfc-style,
skopeo,
xonsh,
# Lix specific packages
pre-commit-checks,
contribNotice,
+ check-syscalls,
}:
let
glibcFix = lib.optionalAttrs (buildPlatform.isLinux && glibcLocales != null) {
@@ -456,11 +456,12 @@ stdenv.mkDerivation (finalAttrs: {
# `bash` from inside `nix develop`, say, because you are using it
# via direnv, you will by default get bash (unusable edition).
bashInteractive
+ check-syscalls
pythonEnv
# docker image tool
skopeo
just
- nixfmt
+ nixfmt-rfc-style
# Included above when internalApiDocs is true, but we set that to
# false intentionally to save dev build time.
# To build them in a dev shell, you can set -Dinternal-api-docs=enabled when configuring.
diff --git a/releng/README.md b/releng/README.md
index cfacf4b8e..2aa3b959f 100644
--- a/releng/README.md
+++ b/releng/README.md
@@ -30,7 +30,7 @@ First, we prepare the release. `python -m releng prepare` is used for this.
Then we tag the release with `python -m releng tag`:
* Git HEAD is detached.
-* `officialRelease = true` is set in `flake.nix`, this is committed, and a
+* `"official_release": true` is set in `version.json`, this is committed, and a
release is tagged.
* The tag is merged back into the last branch (either `main` for new releases
or `release-MAJOR` for maintenance releases) with `git merge -s ours VERSION`
diff --git a/releng/create_release.xsh b/releng/create_release.xsh
index 358124359..62114350b 100644
--- a/releng/create_release.xsh
+++ b/releng/create_release.xsh
@@ -11,7 +11,7 @@ from . import environment
from .environment import RelengEnvironment
from . import keys
from . import docker
-from .version import VERSION, RELEASE_NAME, MAJOR
+from .version import VERSION, RELEASE_NAME, MAJOR, OFFICIAL_RELEASE
from .gitutils import verify_are_on_tag, git_preconditions
from . import release_notes
@@ -39,12 +39,18 @@ def setup_creds(env: RelengEnvironment):
def official_release_commit_tag(force_tag=False):
- print('[+] Setting officialRelease in flake.nix and tagging')
+ print('[+] Setting officialRelease in version.json and tagging')
prev_branch = $(git symbolic-ref --short HEAD).strip()
git switch --detach
- sed -i 's/officialRelease = false/officialRelease = true/' flake.nix
- git add flake.nix
+
+ # Must be done in two parts due to buffering (opening the file immediately
+ # would truncate it).
+ new_version_json = $(jq --indent 4 '.official_release = true' version.json)
+ with open('version.json', 'w') as fh:
+ fh.write(new_version_json)
+ git add version.json
+
message = f'release: {VERSION} "{RELEASE_NAME}"\n\nRelease produced with releng/create_release.xsh'
git commit -m @(message)
git tag @(['-f'] if force_tag else []) -a -m @(message) @(VERSION)
@@ -250,15 +256,14 @@ def build_manual(eval_result):
def upload_manual(env: RelengEnvironment):
- stable = json.loads($(nix eval --json '.#nix.officialRelease'))
- if stable:
+ if OFFICIAL_RELEASE:
version = MAJOR
else:
version = 'nightly'
print('[+] aws s3 sync manual')
aws s3 sync @(MANUAL)/ @(env.docs_bucket)/manual/lix/@(version)/
- if stable:
+ if OFFICIAL_RELEASE:
aws s3 sync @(MANUAL)/ @(env.docs_bucket)/manual/lix/stable/
diff --git a/releng/docker.xsh b/releng/docker.xsh
index 13bdd7868..2ea7aecff 100644
--- a/releng/docker.xsh
+++ b/releng/docker.xsh
@@ -44,23 +44,8 @@ def upload_docker_images(target: DockerTarget, paths: list[Path]):
for path in paths:
digest_file = tmp / (path.name + '.digest')
- tmp_image = tmp / 'tmp-image.tar.gz'
- # insecure-policy: we don't have any signature policy, we are just uploading an image
- #
- # Absurd: we copy it into an OCI image first so we can get the hash
- # we need to upload it untagged, because skopeo has no "don't tag
- # this" option.
- # The reason for this is that forgejo's container registry throws
- # away old versions of tags immediately, so we cannot use a temp
- # tag, and it *does* reduce confusion to not upload tags that
- # should not be used.
- #
- # Workaround for: https://github.com/containers/skopeo/issues/2354
- log.info('skopeo copy to temp oci-archive %s', tmp_image)
- skopeo --insecure-policy copy --format oci --all --digestfile @(digest_file) docker-archive:@(path) oci-archive:@(tmp_image)
-
- inspection = json.loads($(skopeo inspect oci-archive:@(tmp_image)))
+ inspection = json.loads($(skopeo inspect docker-archive:@(path)))
docker_arch = inspection['Architecture']
docker_os = inspection['Os']
@@ -68,8 +53,9 @@ def upload_docker_images(target: DockerTarget, paths: list[Path]):
log.info('Pushing image %s for %s to %s', path, docker_arch, target.registry_path)
+ # insecure-policy: we don't have any signature policy, we are just uploading an image
+ skopeo --insecure-policy copy --digestfile @(digest_file) --all docker-archive:@(path) f'docker://{target.registry_path}@@unknown-digest@@'
digest = digest_file.read_text().strip()
- skopeo --insecure-policy copy --preserve-digests --all oci-archive:@(tmp_image) f'docker://{target.registry_path}@{digest}'
# skopeo doesn't give us the manifest size directly, so we just ask the registry
metadata = reg.image_info(target.registry_path, digest)
diff --git a/releng/version.py b/releng/version.py
index 47ef23504..4ad188d46 100644
--- a/releng/version.py
+++ b/releng/version.py
@@ -4,3 +4,4 @@ version_json = json.load(open('version.json'))
VERSION = version_json['version']
MAJOR = '.'.join(VERSION.split('.')[:2])
RELEASE_NAME = version_json['release_name']
+OFFICIAL_RELEASE = version_json['official_release']
diff --git a/src/asan-options/asan-options.cc b/src/asan-options/asan-options.cc
new file mode 100644
index 000000000..c4cf360af
--- /dev/null
+++ b/src/asan-options/asan-options.cc
@@ -0,0 +1,17 @@
+/// @file This is very bothersome code that has to be included in every
+/// executable to get the correct default ASan options. I am so sorry.
+
+extern "C" [[gnu::retain]] const char *__asan_default_options()
+{
+ // We leak a bunch of memory knowingly on purpose. It's not worthwhile to
+ // diagnose that memory being leaked for now.
+ //
+ // Instruction bytes are useful for finding the actual code that
+ // corresponds to an ASan report.
+ //
+ // TODO: setting log_path=asan.log or not: neither works, since you can't
+ // write to the fs in certain places in the testsuite, but you also cannot
+ // write arbitrarily to stderr in other places so the reports get eaten.
+ // pain 🥖
+ return "halt_on_error=1:abort_on_error=1:detect_leaks=0:print_summary=1:dump_instruction_bytes=1";
+}
diff --git a/src/build-remote/build-remote.cc b/src/build-remote/build-remote.cc
index fb90403a0..99bbc62d5 100644
--- a/src/build-remote/build-remote.cc
+++ b/src/build-remote/build-remote.cc
@@ -236,9 +236,9 @@ static int main_build_remote(int argc, char * * argv)
}
#if __APPLE__
- futimes(bestSlotLock.get(), NULL);
+ futimes(bestSlotLock.get(), nullptr);
#else
- futimens(bestSlotLock.get(), NULL);
+ futimens(bestSlotLock.get(), nullptr);
#endif
lock.reset();
diff --git a/src/libcmd/installable-attr-path.cc b/src/libcmd/installable-attr-path.cc
index eb15fecc3..9891b263c 100644
--- a/src/libcmd/installable-attr-path.cc
+++ b/src/libcmd/installable-attr-path.cc
@@ -1,23 +1,11 @@
-#include "globals.hh"
#include "installable-attr-path.hh"
#include "outputs-spec.hh"
#include "command.hh"
#include "attr-path.hh"
#include "common-eval-args.hh"
-#include "derivations.hh"
-#include "eval-inline.hh"
#include "eval.hh"
#include "get-drvs.hh"
-#include "store-api.hh"
-#include "shared.hh"
#include "flake/flake.hh"
-#include "eval-cache.hh"
-#include "url.hh"
-#include "registry.hh"
-#include "build-result.hh"
-
-#include <regex>
-#include <queue>
#include <nlohmann/json.hpp>
diff --git a/src/libcmd/installable-attr-path.hh b/src/libcmd/installable-attr-path.hh
index 86c2f8219..edc9c2906 100644
--- a/src/libcmd/installable-attr-path.hh
+++ b/src/libcmd/installable-attr-path.hh
@@ -1,25 +1,11 @@
#pragma once
///@file
-#include "globals.hh"
#include "installable-value.hh"
#include "outputs-spec.hh"
#include "command.hh"
-#include "attr-path.hh"
#include "common-eval-args.hh"
-#include "derivations.hh"
-#include "eval-inline.hh"
#include "eval.hh"
-#include "get-drvs.hh"
-#include "store-api.hh"
-#include "shared.hh"
-#include "eval-cache.hh"
-#include "url.hh"
-#include "registry.hh"
-#include "build-result.hh"
-
-#include <regex>
-#include <queue>
#include <nlohmann/json.hpp>
diff --git a/src/libcmd/repl.cc b/src/libcmd/repl.cc
index 635b54958..5086e9999 100644
--- a/src/libcmd/repl.cc
+++ b/src/libcmd/repl.cc
@@ -652,9 +652,12 @@ ProcessLineResult NixRepl::processLine(std::string line)
// using runProgram2 to allow editors to display their UI
runProgram2(RunOptions { .program = editor, .searchPath = true, .args = args }).wait();
- // Reload right after exiting the editor
- state->resetFileCache();
- reloadFiles();
+ // Reload right after exiting the editor if path is not in store
+ // Store is immutable, so there could be no changes, so there's no need to reload
+ if (!state->store->isInStore(path.resolveSymlinks().path.abs())) {
+ state->resetFileCache();
+ reloadFiles();
+ }
}
else if (command == ":t") {
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index c0e7a9a2e..a925ce2d8 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -494,6 +494,14 @@ std::ostream & operator<<(std::ostream & output, PrimOp & primOp)
}
+Value::Value(primop_t, PrimOp & primop)
+ : internalType(tPrimOp)
+ , primOp(&primop)
+ , _primop_pad(0)
+{
+ primop.check();
+}
+
PrimOp * Value::primOpAppPrimOp() const
{
Value * left = primOpApp.left;
@@ -506,7 +514,6 @@ PrimOp * Value::primOpAppPrimOp() const
return left->primOp;
}
-
void Value::mkPrimOp(PrimOp * p)
{
p->check();
diff --git a/src/libexpr/flake/flakeref.cc b/src/libexpr/flake/flakeref.cc
index a95df04ba..3be4ea550 100644
--- a/src/libexpr/flake/flakeref.cc
+++ b/src/libexpr/flake/flakeref.cc
@@ -169,14 +169,13 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
if (subdir != "") {
if (parsedURL.query.count("dir"))
throw Error("flake URL '%s' has an inconsistent 'dir' parameter", url);
- parsedURL.query.insert_or_assign("dir", subdir);
}
if (pathExists(flakeRoot + "/.git/shallow"))
parsedURL.query.insert_or_assign("shallow", "1");
return std::make_pair(
- FlakeRef(Input::fromURL(parsedURL, isFlake), getOr(parsedURL.query, "dir", "")),
+ FlakeRef(Input::fromURL(parsedURL, isFlake), subdir),
fragment);
}
@@ -204,7 +203,13 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
std::string fragment;
std::swap(fragment, parsedURL.fragment);
- auto input = Input::fromURL(parsedURL, isFlake);
+ // This has a special meaning for flakes and must not be passed to libfetchers.
+ // Of course this means that libfetchers cannot have fetchers
+ // expecting an argument `dir` 🫠
+ ParsedURL urlForFetchers(parsedURL);
+ urlForFetchers.query.erase("dir");
+
+ auto input = Input::fromURL(urlForFetchers, isFlake);
input.parent = baseDir;
return std::make_pair(
diff --git a/src/libexpr/gc-alloc.hh b/src/libexpr/gc-alloc.hh
index 04ac28ea8..fc034045f 100644
--- a/src/libexpr/gc-alloc.hh
+++ b/src/libexpr/gc-alloc.hh
@@ -10,6 +10,8 @@
#include <string_view>
#include <vector>
+#include "checked-arithmetic.hh"
+
#if HAVE_BOEHMGC
#include <functional> // std::less
#include <utility> // std::pair
@@ -18,8 +20,6 @@
#include <gc/gc_allocator.h>
#include <gc/gc_cpp.h>
-#include "checked-arithmetic.hh"
-
/// calloc, transparently GC-enabled.
#define LIX_GC_CALLOC(size) GC_MALLOC(size)
diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc
index 8e3969aac..d7869d09b 100644
--- a/src/libexpr/get-drvs.cc
+++ b/src/libexpr/get-drvs.cc
@@ -1,6 +1,7 @@
#include "get-drvs.hh"
#include "eval-inline.hh"
#include "derivations.hh"
+#include "eval.hh"
#include "store-api.hh"
#include "path-with-outputs.hh"
@@ -101,66 +102,134 @@ StorePath DrvInfo::queryOutPath()
return *outPath;
}
+void DrvInfo::fillOutputs(bool withPaths)
+{
+ auto fillDefault = [&]() {
+ std::optional<StorePath> outPath = std::nullopt;
+ if (withPaths) {
+ outPath.emplace(this->queryOutPath());
+ }
+ this->outputs.emplace("out", outPath);
+ };
+
+ // lol. lmao even.
+ if (this->attrs == nullptr) {
+ fillDefault();
+ return;
+ }
+
+ Attr * outputs = this->attrs->get(this->state->sOutputs);
+ if (outputs == nullptr) {
+ fillDefault();
+ return;
+ }
+
+ // NOTE(Qyriad): I don't think there is any codepath that can cause this to error.
+ this->state->forceList(
+ *outputs->value,
+ outputs->pos,
+ "while evaluating the 'outputs' attribute of a derivation"
+ );
+
+ for (auto [idx, elem] : enumerate(outputs->value->listItems())) {
+ // NOTE(Qyriad): This error should be *extremely* rare in practice.
+ // It is impossible to construct with `stdenv.mkDerivation`,
+ // `builtins.derivation`, or even `derivationStrict`. As far as we can tell,
+ // it is only possible by overriding a derivation attrset already created by
+ // one of those with `//` to introduce the failing `outputs` entry.
+ auto errMsg = fmt("while evaluating output %d of a derivation", idx);
+ std::string_view outputName = state->forceStringNoCtx(
+ *elem,
+ outputs->pos,
+ errMsg
+ );
+
+ if (withPaths) {
+ // Find the attr with this output's name...
+ Attr * out = this->attrs->get(this->state->symbols.create(outputName));
+ if (out == nullptr) {
+ // FIXME: throw error?
+ continue;
+ }
+
+ // Meanwhile we couldn't figure out any circumstances
+ // that cause this to error.
+ state->forceAttrs(*out->value, outputs->pos, errMsg);
+
+ // ...and evaluate its `outPath` attribute.
+ Attr * outPath = out->value->attrs->get(this->state->sOutPath);
+ if (outPath == nullptr) {
+ continue;
+ // FIXME: throw error?
+ }
+
+ NixStringContext context;
+ // And idk what could possibly cause this one to error
+ // that wouldn't error before here.
+ auto storePath = state->coerceToStorePath(
+ outPath->pos,
+ *outPath->value,
+ context,
+ errMsg
+ );
+ this->outputs.emplace(outputName, storePath);
+ } else {
+ this->outputs.emplace(outputName, std::nullopt);
+ }
+ }
+}
DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall)
{
+ // If we haven't already cached the outputs set, then do so now.
if (outputs.empty()) {
- /* Get the ‘outputs’ list. */
- Bindings::iterator i;
- if (attrs && (i = attrs->find(state->sOutputs)) != attrs->end()) {
- state->forceList(*i->value, i->pos, "while evaluating the 'outputs' attribute of a derivation");
-
- /* For each output... */
- for (auto elem : i->value->listItems()) {
- std::string output(state->forceStringNoCtx(*elem, i->pos, "while evaluating the name of an output of a derivation"));
-
- if (withPaths) {
- /* Evaluate the corresponding set. */
- Bindings::iterator out = attrs->find(state->symbols.create(output));
- if (out == attrs->end()) continue; // FIXME: throw error?
- state->forceAttrs(*out->value, i->pos, "while evaluating an output of a derivation");
-
- /* And evaluate its ‘outPath’ attribute. */
- Bindings::iterator outPath = out->value->attrs->find(state->sOutPath);
- if (outPath == out->value->attrs->end()) continue; // FIXME: throw error?
- NixStringContext context;
- outputs.emplace(output, state->coerceToStorePath(outPath->pos, *outPath->value, context, "while evaluating an output path of a derivation"));
- } else
- outputs.emplace(output, std::nullopt);
- }
- } else
- outputs.emplace("out", withPaths ? std::optional{queryOutPath()} : std::nullopt);
+ // FIXME: this behavior seems kind of busted, since whether or not this
+ // DrvInfo will have paths is forever determined by the *first* call to
+ // this function??
+ fillOutputs(withPaths);
}
+ // Things that operate on derivations like packages, like `nix-env` and `nix build`,
+ // allow derivations to specify which outputs should be used in those user-facing
+ // cases if the user didn't specify an output explicitly.
+ // If the caller just wanted all the outputs for this derivation, though,
+ // then we're done here.
if (!onlyOutputsToInstall || !attrs)
return outputs;
- Bindings::iterator i;
- if (attrs && (i = attrs->find(state->sOutputSpecified)) != attrs->end() && state->forceBool(*i->value, i->pos, "while evaluating the 'outputSpecified' attribute of a derivation")) {
- Outputs result;
- auto out = outputs.find(queryOutputName());
- if (out == outputs.end())
- throw Error("derivation does not have output '%s'", queryOutputName());
- result.insert(*out);
- return result;
+ // Regardless of `meta.outputsToInstall`, though, you can select into a derivation
+ // output by its attribute, e.g. `pkgs.lix.dev`, which (lol?) sets the magic
+ // attribute `outputSpecified = true`, and changes the `outputName` attr to the
+ // explicitly selected-into output.
+ if (Attr * outSpecAttr = attrs->get(state->sOutputSpecified)) {
+ bool outputSpecified = this->state->forceBool(
+ *outSpecAttr->value,
+ outSpecAttr->pos,
+ "while evaluating the 'outputSpecified' attribute of a derivation"
+ );
+ if (outputSpecified) {
+ auto maybeOut = outputs.find(queryOutputName());
+ if (maybeOut == outputs.end()) {
+ throw Error("derivation does not have output '%s'", queryOutputName());
+ }
+ return Outputs{*maybeOut};
+ }
}
- else {
- /* Check for `meta.outputsToInstall` and return `outputs` reduced to that. */
- const Value * outTI = queryMeta("outputsToInstall");
- if (!outTI) return outputs;
- auto errMsg = Error("this derivation has bad 'meta.outputsToInstall'");
- /* ^ this shows during `nix-env -i` right under the bad derivation */
- if (!outTI->isList()) throw errMsg;
- Outputs result;
- for (auto elem : outTI->listItems()) {
- if (elem->type() != nString) throw errMsg;
- auto out = outputs.find(elem->string.s);
- if (out == outputs.end()) throw errMsg;
- result.insert(*out);
- }
- return result;
+ /* Check for `meta.outputsToInstall` and return `outputs` reduced to that. */
+ const Value * outTI = queryMeta("outputsToInstall");
+ if (!outTI) return outputs;
+ auto errMsg = Error("this derivation has bad 'meta.outputsToInstall'");
+ /* ^ this shows during `nix-env -i` right under the bad derivation */
+ if (!outTI->isList()) throw errMsg;
+ Outputs result;
+ for (auto elem : outTI->listItems()) {
+ if (elem->type() != nString) throw errMsg;
+ auto out = outputs.find(elem->string.s);
+ if (out == outputs.end()) throw errMsg;
+ result.insert(*out);
}
+ return result;
}
@@ -350,56 +419,95 @@ static void getDerivations(EvalState & state, Value & vIn,
Value v;
state.autoCallFunction(autoArgs, vIn, v);
- /* Process the expression. */
- if (!getDerivation(state, v, pathPrefix, drvs, ignoreAssertionFailures)) ;
-
- else if (v.type() == nAttrs) {
-
- /* Dont consider sets we've already seen, e.g. y in
- `rec { x.d = derivation {...}; y = x; }`. */
- if (!done.insert(v.attrs).second) return;
-
- /* !!! undocumented hackery to support combining channels in
- nix-env.cc. */
- bool combineChannels = v.attrs->find(state.symbols.create("_combineChannels")) != v.attrs->end();
-
- /* Consider the attributes in sorted order to get more
- deterministic behaviour in nix-env operations (e.g. when
- there are names clashes between derivations, the derivation
- bound to the attribute with the "lower" name should take
- precedence). */
- for (auto & i : v.attrs->lexicographicOrder(state.symbols)) {
- debug("evaluating attribute '%1%'", state.symbols[i->name]);
- if (!std::regex_match(std::string(state.symbols[i->name]), attrRegex))
- continue;
- std::string pathPrefix2 = addToPath(pathPrefix, state.symbols[i->name]);
- if (combineChannels)
- getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
- else if (getDerivation(state, *i->value, pathPrefix2, drvs, ignoreAssertionFailures)) {
- /* If the value of this attribute is itself a set,
- should we recurse into it? => Only if it has a
- `recurseForDerivations = true' attribute. */
- if (i->value->type() == nAttrs) {
- Bindings::iterator j = i->value->attrs->find(state.sRecurseForDerivations);
- if (j != i->value->attrs->end() && state.forceBool(*j->value, j->pos, "while evaluating the attribute `recurseForDerivations`"))
- getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
- }
- }
- }
+ bool shouldRecurse = getDerivation(state, v, pathPrefix, drvs, ignoreAssertionFailures);
+ if (!shouldRecurse) {
+ // We're done here.
+ return;
}
- else if (v.type() == nList) {
+ if (v.type() == nList) {
// NOTE we can't really deduplicate here because small lists don't have stable addresses
// and can cause spurious duplicate detections due to v being on the stack.
for (auto [n, elem] : enumerate(v.listItems())) {
- std::string pathPrefix2 = addToPath(pathPrefix, fmt("%d", n));
- if (getDerivation(state, *elem, pathPrefix2, drvs, ignoreAssertionFailures))
- getDerivations(state, *elem, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
+ std::string joinedAttrPath = addToPath(pathPrefix, fmt("%d", n));
+ bool shouldRecurse = getDerivation(state, *elem, joinedAttrPath, drvs, ignoreAssertionFailures);
+ if (shouldRecurse) {
+ getDerivations(
+ state,
+ *elem,
+ joinedAttrPath,
+ autoArgs,
+ drvs,
+ done,
+ ignoreAssertionFailures
+ );
+ }
}
+
+ return;
+ } else if (v.type() != nAttrs) {
+ state.error<TypeError>(
+ "expression was expected to be a derivation or collection of derivations, but instead was %s",
+ showType(v.type(), true)
+ ).debugThrow();
+ }
+
+ /* Dont consider sets we've already seen, e.g. y in
+ `rec { x.d = derivation {...}; y = x; }`. */
+ auto const &[_, didInsert] = done.insert(v.attrs);
+ if (!didInsert) {
+ return;
}
- else
- state.error<TypeError>("expression does not evaluate to a derivation (or a set or list of those)").debugThrow();
+ // FIXME: what the fuck???
+ /* !!! undocumented hackery to support combining channels in
+ nix-env.cc. */
+ bool combineChannels = v.attrs->find(state.symbols.create("_combineChannels")) != v.attrs->end();
+
+ /* Consider the attributes in sorted order to get more
+ deterministic behaviour in nix-env operations (e.g. when
+ there are names clashes between derivations, the derivation
+ bound to the attribute with the "lower" name should take
+ precedence). */
+ for (auto & attr : v.attrs->lexicographicOrder(state.symbols)) {
+ debug("evaluating attribute '%1%'", state.symbols[attr->name]);
+ // FIXME: only consider attrs with identifier-like names?? Why???
+ if (!std::regex_match(std::string(state.symbols[attr->name]), attrRegex)) {
+ continue;
+ }
+ std::string joinedAttrPath = addToPath(pathPrefix, state.symbols[attr->name]);
+ if (combineChannels) {
+ getDerivations(state, *attr->value, joinedAttrPath, autoArgs, drvs, done, ignoreAssertionFailures);
+ } else if (getDerivation(state, *attr->value, joinedAttrPath, drvs, ignoreAssertionFailures)) {
+ /* If the value of this attribute is itself a set,
+ should we recurse into it? => Only if it has a
+ `recurseForDerivations = true' attribute. */
+ if (attr->value->type() == nAttrs) {
+ Attr * recurseForDrvs = attr->value->attrs->get(state.sRecurseForDerivations);
+ if (recurseForDrvs == nullptr) {
+ continue;
+ }
+ bool shouldRecurse = state.forceBool(
+ *recurseForDrvs->value,
+ attr->pos,
+ fmt("while evaluating the '%s' attribute", Magenta("recurseForDerivations"))
+ );
+ if (!shouldRecurse) {
+ continue;
+ }
+
+ getDerivations(
+ state,
+ *attr->value,
+ joinedAttrPath,
+ autoArgs,
+ drvs,
+ done,
+ ignoreAssertionFailures
+ );
+ }
+ }
+ }
}
diff --git a/src/libexpr/get-drvs.hh b/src/libexpr/get-drvs.hh
index 44aac3015..fd927b9e5 100644
--- a/src/libexpr/get-drvs.hh
+++ b/src/libexpr/get-drvs.hh
@@ -37,13 +37,14 @@ private:
bool checkMeta(Value & v);
+ void fillOutputs(bool withPaths = true);
+
public:
/**
* path towards the derivation
*/
std::string attrPath;
- DrvInfo(EvalState & state) : state(&state) { };
DrvInfo(EvalState & state, std::string attrPath, Bindings * attrs);
DrvInfo(EvalState & state, ref<Store> store, const std::string & drvPathWithOutputs);
diff --git a/src/libexpr/json-to-value.cc b/src/libexpr/json-to-value.cc
index b69216a48..2309b6c97 100644
--- a/src/libexpr/json-to-value.cc
+++ b/src/libexpr/json-to-value.cc
@@ -82,28 +82,28 @@ class JSONSax : nlohmann::json_sax<json> {
public:
JSONSax(EvalState & state, Value & v) : state(state), rs(new JSONState(&v)) {};
- bool null()
+ bool null() override
{
rs->value(state).mkNull();
rs->add();
return true;
}
- bool boolean(bool val)
+ bool boolean(bool val) override
{
rs->value(state).mkBool(val);
rs->add();
return true;
}
- bool number_integer(number_integer_t val)
+ bool number_integer(number_integer_t val) override
{
rs->value(state).mkInt(val);
rs->add();
return true;
}
- bool number_unsigned(number_unsigned_t val_)
+ bool number_unsigned(number_unsigned_t val_) override
{
if (val_ > std::numeric_limits<NixInt::Inner>::max()) {
throw Error("unsigned json number %1% outside of Nix integer range", val_);
@@ -114,14 +114,14 @@ public:
return true;
}
- bool number_float(number_float_t val, const string_t & s)
+ bool number_float(number_float_t val, const string_t & s) override
{
rs->value(state).mkFloat(val);
rs->add();
return true;
}
- bool string(string_t & val)
+ bool string(string_t & val) override
{
rs->value(state).mkString(val);
rs->add();
@@ -129,7 +129,7 @@ public:
}
#if NLOHMANN_JSON_VERSION_MAJOR >= 3 && NLOHMANN_JSON_VERSION_MINOR >= 8
- bool binary(binary_t&)
+ bool binary(binary_t&) override
{
// This function ought to be unreachable
assert(false);
@@ -137,35 +137,37 @@ public:
}
#endif
- bool start_object(std::size_t len)
+ bool start_object(std::size_t len) override
{
rs = std::make_unique<JSONObjectState>(std::move(rs));
return true;
}
- bool key(string_t & name)
+ bool key(string_t & name) override
{
dynamic_cast<JSONObjectState*>(rs.get())->key(name, state);
return true;
}
- bool end_object() {
+ bool end_object() override {
rs = rs->resolve(state);
rs->add();
return true;
}
- bool end_array() {
+ bool end_array() override {
return end_object();
}
- bool start_array(size_t len) {
+ bool start_array(size_t len) override {
rs = std::make_unique<JSONListState>(std::move(rs),
len != std::numeric_limits<size_t>::max() ? len : 128);
return true;
}
- bool parse_error(std::size_t, const std::string&, const nlohmann::detail::exception& ex) {
+ bool
+ parse_error(std::size_t, const std::string &, const nlohmann::detail::exception & ex) override
+ {
throw JSONParseError("%s", ex.what());
}
};
diff --git a/src/libexpr/parser/parser.cc b/src/libexpr/parser/parser.cc
index e60cf967f..a00586c36 100644
--- a/src/libexpr/parser/parser.cc
+++ b/src/libexpr/parser/parser.cc
@@ -43,7 +43,6 @@ error_message_for(p::one<'}'>) = "expecting '}'";
error_message_for(p::one<'"'>) = "expecting '\"'";
error_message_for(p::one<';'>) = "expecting ';'";
error_message_for(p::one<')'>) = "expecting ')'";
-error_message_for(p::one<'='>) = "expecting '='";
error_message_for(p::one<']'>) = "expecting ']'";
error_message_for(p::one<':'>) = "expecting ':'";
error_message_for(p::string<'\'', '\''>) = "expecting \"''\"";
diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh
index c35f88f8d..57485aa0a 100644
--- a/src/libexpr/value.hh
+++ b/src/libexpr/value.hh
@@ -3,6 +3,9 @@
#include <cassert>
#include <climits>
+#include <functional>
+#include <ranges>
+#include <span>
#include "gc-alloc.hh"
#include "symbol-table.hh"
@@ -11,6 +14,7 @@
#include "source-path.hh"
#include "print-options.hh"
#include "checked-arithmetic.hh"
+#include "concepts.hh"
#include <nlohmann/json_fwd.hpp>
@@ -132,6 +136,55 @@ class ExternalValueBase
std::ostream & operator << (std::ostream & str, const ExternalValueBase & v);
+extern ExprBlackHole eBlackHole;
+
+struct NewValueAs
+{
+ struct integer_t { };
+ constexpr static integer_t integer{};
+
+ struct floating_t { };
+ constexpr static floating_t floating{};
+
+ struct boolean_t { };
+ constexpr static boolean_t boolean{};
+
+ struct string_t { };
+ constexpr static string_t string{};
+
+ struct path_t { };
+ constexpr static path_t path{};
+
+ struct list_t { };
+ constexpr static list_t list{};
+
+ struct attrs_t { };
+ constexpr static attrs_t attrs{};
+
+ struct thunk_t { };
+ constexpr static thunk_t thunk{};
+
+ struct null_t { };
+ constexpr static null_t null{};
+
+ struct app_t { };
+ constexpr static app_t app{};
+
+ struct primop_t { };
+ constexpr static primop_t primop{};
+
+ struct primOpApp_t { };
+ constexpr static primOpApp_t primOpApp{};
+
+ struct lambda_t { };
+ constexpr static lambda_t lambda{};
+
+ struct external_t { };
+ constexpr static external_t external{};
+
+ struct blackhole_t { };
+ constexpr static blackhole_t blackhole{};
+};
struct Value
{
@@ -142,6 +195,315 @@ private:
public:
+ // Discount `using NewValueAs::*;`
+#define USING_VALUETYPE(name) using name = NewValueAs::name
+ USING_VALUETYPE(integer_t);
+ USING_VALUETYPE(floating_t);
+ USING_VALUETYPE(boolean_t);
+ USING_VALUETYPE(string_t);
+ USING_VALUETYPE(path_t);
+ USING_VALUETYPE(list_t);
+ USING_VALUETYPE(attrs_t);
+ USING_VALUETYPE(thunk_t);
+ USING_VALUETYPE(primop_t);
+ USING_VALUETYPE(app_t);
+ USING_VALUETYPE(null_t);
+ USING_VALUETYPE(primOpApp_t);
+ USING_VALUETYPE(lambda_t);
+ USING_VALUETYPE(external_t);
+ USING_VALUETYPE(blackhole_t);
+#undef USING_VALUETYPE
+
+ /// Default constructor which is still used in the codebase but should not
+ /// be used in new code. Zero initializes its members.
+ [[deprecated]] Value()
+ : internalType(static_cast<InternalType>(0))
+ , _empty{ 0, 0 }
+ { }
+
+ /// Constructs a nix language value of type "int", with the integral value
+ /// of @ref i.
+ Value(integer_t, NixInt i)
+ : internalType(tInt)
+ , _empty{ 0, 0 }
+ {
+ // the NixInt ctor here is is special because NixInt has a ctor too, so
+ // we're not allowed to have it as an anonymous aggreagte member. we do
+ // however still have the option to clear the data members using _empty
+ // and leaving the second word of data cleared by setting only integer.
+ integer = i;
+ }
+
+ /// Constructs a nix language value of type "float", with the floating
+ /// point value of @ref f.
+ Value(floating_t, NixFloat f)
+ : internalType(tFloat)
+ , fpoint(f)
+ , _float_pad(0)
+ { }
+
+ /// Constructs a nix language value of type "bool", with the boolean
+ /// value of @ref b.
+ Value(boolean_t, bool b)
+ : internalType(tBool)
+ , boolean(b)
+ , _bool_pad(0)
+ { }
+
+ /// Constructs a nix language value of type "string", with the value of the
+ /// C-string pointed to by @ref strPtr, and optionally with an array of
+ /// string context pointed to by @ref contextPtr.
+ ///
+ /// Neither the C-string nor the context array are copied; this constructor
+ /// assumes suitable memory has already been allocated (with the GC if
+ /// enabled), and string and context data copied into that memory.
+ Value(string_t, char const * strPtr, char const ** contextPtr = nullptr)
+ : internalType(tString)
+ , string({ .s = strPtr, .context = contextPtr })
+ { }
+
+ /// Constructx a nix language value of type "string", with a copy of the
+ /// string data viewed by @ref copyFrom.
+ ///
+ /// The string data *is* copied from @ref copyFrom, and this constructor
+ /// performs a dynamic (GC) allocation to do so.
+ Value(string_t, std::string_view copyFrom, NixStringContext const & context = {})
+ : internalType(tString)
+ , string({ .s = gcCopyStringIfNeeded(copyFrom), .context = nullptr })
+ {
+ if (context.empty()) {
+ // It stays nullptr.
+ return;
+ }
+
+ // Copy the context.
+ this->string.context = gcAllocType<char const *>(context.size() + 1);
+
+ size_t n = 0;
+ for (NixStringContextElem const & contextElem : context) {
+ this->string.context[n] = gcCopyStringIfNeeded(contextElem.to_string());
+ n += 1;
+ }
+
+ // Terminator sentinel.
+ this->string.context[n] = nullptr;
+ }
+
+ /// Constructx a nix language value of type "string", with the value of the
+ /// C-string pointed to by @ref strPtr, and optionally with a set of string
+ /// context @ref context.
+ ///
+ /// The C-string is not copied; this constructor assumes suitable memory
+ /// has already been allocated (with the GC if enabled), and string data
+ /// has been copied into that memory. The context data *is* copied from
+ /// @ref context, and this constructor performs a dynamic (GC) allocation
+ /// to do so.
+ Value(string_t, char const * strPtr, NixStringContext const & context)
+ : internalType(tString)
+ , string({ .s = strPtr, .context = nullptr })
+ {
+ if (context.empty()) {
+ // It stays nullptr
+ return;
+ }
+
+ // Copy the context.
+ this->string.context = gcAllocType<char const *>(context.size() + 1);
+
+ size_t n = 0;
+ for (NixStringContextElem const & contextElem : context) {
+ this->string.context[n] = gcCopyStringIfNeeded(contextElem.to_string());
+ n += 1;
+ }
+
+ // Terminator sentinel.
+ this->string.context[n] = nullptr;
+ }
+
+ /// Constructs a nix language value of type "path", with the value of the
+ /// C-string pointed to by @ref strPtr.
+ ///
+ /// The C-string is not copied; this constructor assumes suitable memory
+ /// has already been allocated (with the GC if enabled), and string data
+ /// has been copied into that memory.
+ Value(path_t, char const * strPtr)
+ : internalType(tPath)
+ , _path(strPtr)
+ , _path_pad(0)
+ { }
+
+ /// Constructs a nix language value of type "path", with the path
+ /// @ref path.
+ ///
+ /// The data from @ref path *is* copied, and this constructor performs a
+ /// dynamic (GC) allocation to do so.
+ Value(path_t, SourcePath const & path)
+ : internalType(tPath)
+ , _path(gcCopyStringIfNeeded(path.path.abs()))
+ , _path_pad(0)
+ { }
+
+ /// Constructs a nix language value of type "list", with element array
+ /// @ref items.
+ ///
+ /// Generally, the data in @ref items is neither deep copied nor shallow
+ /// copied. This construct assumes the std::span @ref items is a region of
+ /// memory that has already been allocated (with the GC if enabled), and
+ /// an array of valid Value pointers has been copied into that memory.
+ ///
+ /// Howver, as an implementation detail, if @ref items is only 2 items or
+ /// smaller, the list is stored inline, and the Value pointers in
+ /// @ref items are shallow copied into this structure, without dynamically
+ /// allocating memory.
+ Value(list_t, std::span<Value *> items)
+ {
+ if (items.size() == 1) {
+ this->internalType = tList1;
+ this->smallList[0] = items[0];
+ this->smallList[1] = nullptr;
+ } else if (items.size() == 2) {
+ this->internalType = tList2;
+ this->smallList[0] = items[0];
+ this->smallList[1] = items[1];
+ } else {
+ this->internalType = tListN;
+ this->bigList.size = items.size();
+ this->bigList.elems = items.data();
+ }
+ }
+
+ /// Constructs a nix language value of type "list", with an element array
+ /// initialized by applying @ref transformer to each element in @ref items.
+ ///
+ /// This allows "in-place" construction of a nix list when some logic is
+ /// needed to get each Value pointer. This constructor dynamically (GC)
+ /// allocates memory for the size of @ref items, and the Value pointers
+ /// returned by @ref transformer are shallow copied into it.
+ template<
+ std::ranges::sized_range SizedIterableT,
+ InvocableR<Value *, typename SizedIterableT::value_type const &> TransformerT
+ >
+ Value(list_t, SizedIterableT & items, TransformerT const & transformer)
+ {
+ if (items.size() == 1) {
+ this->internalType = tList1;
+ this->smallList[0] = transformer(*items.begin());
+ this->smallList[1] = nullptr;
+ } else if (items.size() == 2) {
+ this->internalType = tList2;
+ auto it = items.begin();
+ this->smallList[0] = transformer(*it);
+ it++;
+ this->smallList[1] = transformer(*it);
+ } else {
+ this->internalType = tListN;
+ this->bigList.size = items.size();
+ this->bigList.elems = gcAllocType<Value *>(items.size());
+ auto it = items.begin();
+ for (size_t i = 0; i < items.size(); i++, it++) {
+ this->bigList.elems[i] = transformer(*it);
+ }
+ }
+ }
+
+ /// Constructs a nix language value of the singleton type "null".
+ Value(null_t)
+ : internalType(tNull)
+ , _empty{0, 0}
+ { }
+
+ /// Constructs a nix language value of type "set", with the attribute
+ /// bindings pointed to by @ref bindings.
+ ///
+ /// The bindings are not not copied; this constructor assumes @ref bindings
+ /// has already been suitably allocated by something like nix::buildBindings.
+ Value(attrs_t, Bindings * bindings)
+ : internalType(tAttrs)
+ , attrs(bindings)
+ , _attrs_pad(0)
+ { }
+
+ /// Constructs a nix language lazy delayed computation, or "thunk".
+ ///
+ /// The thunk stores the environment it will be computed in @ref env, and
+ /// the expression that will need to be evaluated @ref expr.
+ Value(thunk_t, Env & env, Expr & expr)
+ : internalType(tThunk)
+ , thunk({ .env = &env, .expr = &expr })
+ { }
+
+ /// Constructs a nix language value of type "lambda", which represents
+ /// a builtin, primitive operation ("primop"), from the primop
+ /// implemented by @ref primop.
+ Value(primop_t, PrimOp & primop);
+
+ /// Constructs a nix language value of type "lambda", which represents a
+ /// partially applied primop.
+ Value(primOpApp_t, Value & lhs, Value & rhs)
+ : internalType(tPrimOpApp)
+ , primOpApp({ .left = &lhs, .right = &rhs })
+ { }
+
+ /// Constructs a nix language value of type "lambda", which represents a
+ /// lazy partial application of another lambda.
+ Value(app_t, Value & lhs, Value & rhs)
+ : internalType(tApp)
+ , app({ .left = &lhs, .right = &rhs })
+ { }
+
+ /// Constructs a nix language value of type "external", which is only used
+ /// by plugins. Do any existing plugins even use this mechanism?
+ Value(external_t, ExternalValueBase & external)
+ : internalType(tExternal)
+ , external(&external)
+ , _external_pad(0)
+ { }
+
+ /// Constructs a nix language value of type "lambda", which represents a
+ /// run of the mill lambda defined in nix code.
+ ///
+ /// This takes the environment the lambda is closed over @ref env, and
+ /// the lambda expression itself @ref lambda, which will not be evaluated
+ /// until it is applied.
+ Value(lambda_t, Env & env, ExprLambda & lambda)
+ : internalType(tLambda)
+ , lambda({ .env = &env, .fun = &lambda })
+ { }
+
+ /// Constructs an evil thunk, whose evaluation represents infinite recursion.
+ explicit Value(blackhole_t)
+ : internalType(tThunk)
+ , thunk({ .env = nullptr, .expr = reinterpret_cast<Expr *>(&eBlackHole) })
+ { }
+
+ Value(Value const & rhs) = default;
+
+ /// Move constructor. Does the same thing as the copy constructor, but
+ /// also zeroes out the other Value.
+ Value(Value && rhs)
+ : internalType(rhs.internalType)
+ , _empty{ 0, 0 }
+ {
+ *this = std::move(rhs);
+ }
+
+ Value & operator=(Value const & rhs) = default;
+
+ /// Move assignment operator.
+ /// Does the same thing as the copy assignment operator, but also zeroes out
+ /// the rhs.
+ inline Value & operator=(Value && rhs)
+ {
+ *this = static_cast<const Value &>(rhs);
+ if (this != &rhs) {
+ // Kill `rhs`, because non-destructive move lol.
+ rhs.internalType = static_cast<InternalType>(0);
+ rhs._empty[0] = 0;
+ rhs._empty[1] = 0;
+ }
+ return *this;
+ }
+
void print(EvalState &state, std::ostream &str, PrintOptions options = PrintOptions {});
// Functions needed to distinguish the type
@@ -160,8 +522,15 @@ public:
union
{
+ /// Dummy field, which takes up as much space as the largest union variants
+ /// to set the union's memory to zeroed memory.
+ uintptr_t _empty[2];
+
NixInt integer;
- bool boolean;
+ struct {
+ bool boolean;
+ uintptr_t _bool_pad;
+ };
/**
* Strings in the evaluator carry a so-called `context` which
@@ -190,8 +559,14 @@ public:
const char * * context; // must be in sorted order
} string;
- const char * _path;
- Bindings * attrs;
+ struct {
+ const char * _path;
+ uintptr_t _path_pad;
+ };
+ struct {
+ Bindings * attrs;
+ uintptr_t _attrs_pad;
+ };
struct {
size_t size;
Value * * elems;
@@ -208,12 +583,21 @@ public:
Env * env;
ExprLambda * fun;
} lambda;
- PrimOp * primOp;
+ struct {
+ PrimOp * primOp;
+ uintptr_t _primop_pad;
+ };
struct {
Value * left, * right;
} primOpApp;
- ExternalValueBase * external;
- NixFloat fpoint;
+ struct {
+ ExternalValueBase * external;
+ uintptr_t _external_pad;
+ };
+ struct {
+ NixFloat fpoint;
+ uintptr_t _float_pad;
+ };
};
/**
@@ -449,8 +833,6 @@ public:
};
-extern ExprBlackHole eBlackHole;
-
bool Value::isBlackhole() const
{
return internalType == tThunk && thunk.expr == (Expr*) &eBlackHole;
diff --git a/src/libfetchers/fetchers.hh b/src/libfetchers/fetchers.hh
index 2bb4248be..40f2b6294 100644
--- a/src/libfetchers/fetchers.hh
+++ b/src/libfetchers/fetchers.hh
@@ -159,6 +159,37 @@ struct InputScheme
std::optional<std::string> commitMsg) const;
virtual std::pair<StorePath, Input> fetch(ref<Store> store, const Input & input) = 0;
+
+protected:
+ void emplaceURLQueryIntoAttrs(
+ const ParsedURL & parsedURL,
+ Attrs & attrs,
+ const StringSet & numericParams,
+ const StringSet & booleanParams) const
+ {
+ for (auto &[name, value] : parsedURL.query) {
+ if (name == "url") {
+ throw BadURL(
+ "URL '%s' must not override url via query param!",
+ parsedURL.to_string()
+ );
+ } else if (numericParams.count(name) != 0) {
+ if (auto n = string2Int<uint64_t>(value)) {
+ attrs.insert_or_assign(name, *n);
+ } else {
+ throw BadURL(
+ "URL '%s' has non-numeric parameter '%s'",
+ parsedURL.to_string(),
+ name
+ );
+ }
+ } else if (booleanParams.count(name) != 0) {
+ attrs.emplace(name, Explicit<bool> { value == "1" });
+ } else {
+ attrs.emplace(name, value);
+ }
+ }
+ }
};
void registerInputScheme(std::shared_ptr<InputScheme> && fetcher);
diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc
index 81c579e84..8e3165ff6 100644
--- a/src/libfetchers/git.cc
+++ b/src/libfetchers/git.cc
@@ -273,18 +273,15 @@ struct GitInputScheme : InputScheme
Attrs attrs;
attrs.emplace("type", "git");
-
- for (auto & [name, value] : url.query) {
- if (name == "rev" || name == "ref")
- attrs.emplace(name, value);
- else if (name == "shallow" || name == "submodules" || name == "allRefs")
- attrs.emplace(name, Explicit<bool> { value == "1" });
- else
- url2.query.emplace(name, value);
- }
-
attrs.emplace("url", url2.to_string());
+ emplaceURLQueryIntoAttrs(
+ url,
+ attrs,
+ {"lastModified", "revCount"},
+ {"shallow", "submodules", "allRefs"}
+ );
+
return inputFromAttrs(attrs);
}
@@ -695,7 +692,7 @@ struct GitInputScheme : InputScheme
});
Finally const _wait([&] { proc.wait(); });
- unpackTarfile(*proc.stdout(), tmpDir);
+ unpackTarfile(*proc.getStdout(), tmpDir);
}
auto storePath = store->addToStore(name, tmpDir, FileIngestionMethod::Recursive, htSHA256, filter);
diff --git a/src/libfetchers/github.cc b/src/libfetchers/github.cc
index 60fefd1f3..b971781ae 100644
--- a/src/libfetchers/github.cc
+++ b/src/libfetchers/github.cc
@@ -1,3 +1,4 @@
+#include "attrs.hh"
#include "filetransfer.hh"
#include "cache.hh"
#include "globals.hh"
@@ -36,18 +37,11 @@ struct GitArchiveInputScheme : InputScheme
auto path = tokenizeString<std::vector<std::string>>(url.path, "/");
- std::optional<Hash> rev;
- std::optional<std::string> ref;
- std::optional<std::string> host_url;
+ std::optional<std::string> refOrRev;
auto size = path.size();
if (size == 3) {
- if (std::regex_match(path[2], revRegex))
- rev = Hash::parseAny(path[2], htSHA1);
- else if (std::regex_match(path[2], refRegex))
- ref = path[2];
- else
- throw BadURL("in URL '%s', '%s' is not a commit hash or branch/tag name", url.url, path[2]);
+ refOrRev = path[2];
} else if (size > 3) {
std::string rs;
for (auto i = std::next(path.begin(), 2); i != path.end(); i++) {
@@ -58,61 +52,91 @@ struct GitArchiveInputScheme : InputScheme
}
if (std::regex_match(rs, refRegex)) {
- ref = rs;
+ refOrRev = rs;
} else {
throw BadURL("in URL '%s', '%s' is not a branch/tag name", url.url, rs);
}
} else if (size < 2)
throw BadURL("URL '%s' is invalid", url.url);
+ Attrs attrs;
+ attrs.emplace("type", type());
+ attrs.emplace("owner", path[0]);
+ attrs.emplace("repo", path[1]);
+
for (auto &[name, value] : url.query) {
- if (name == "rev") {
- if (rev)
- throw BadURL("URL '%s' contains multiple commit hashes", url.url);
- rev = Hash::parseAny(value, htSHA1);
- }
- else if (name == "ref") {
- if (!std::regex_match(value, refRegex))
- throw BadURL("URL '%s' contains an invalid branch/tag name", url.url);
- if (ref)
- throw BadURL("URL '%s' contains multiple branch/tag names", url.url);
- ref = value;
- }
- else if (name == "host") {
- if (!std::regex_match(value, hostRegex))
- throw BadURL("URL '%s' contains an invalid instance host", url.url);
- host_url = value;
+ if (name == "rev" || name == "ref") {
+ if (refOrRev) {
+ throw BadURL("URL '%s' already contains a ref or rev", url.url);
+ } else {
+ refOrRev = value;
+ }
+ } else if (name == "lastModified") {
+ if (auto n = string2Int<uint64_t>(value)) {
+ attrs.emplace(name, *n);
+ } else {
+ throw Error(
+ "Attribute 'lastModified' in URL '%s' must be an integer",
+ url.to_string()
+ );
+ }
+ } else {
+ attrs.emplace(name, value);
}
- // FIXME: barf on unsupported attributes
}
- if (ref && rev)
- throw BadURL("URL '%s' contains both a commit hash and a branch/tag name %s %s", url.url, *ref, rev->gitRev());
+ if (refOrRev) attrs.emplace("refOrRev", *refOrRev);
- Input input;
- input.attrs.insert_or_assign("type", type());
- input.attrs.insert_or_assign("owner", path[0]);
- input.attrs.insert_or_assign("repo", path[1]);
- if (rev) input.attrs.insert_or_assign("rev", rev->gitRev());
- if (ref) input.attrs.insert_or_assign("ref", *ref);
- if (host_url) input.attrs.insert_or_assign("host", *host_url);
-
- return input;
+ return inputFromAttrs(attrs);
}
std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
{
- if (maybeGetStrAttr(attrs, "type") != type()) return {};
+ // Attributes can contain refOrRev and it needs to be figured out
+ // which one it is (see inputFromURL for when that may happen).
+ // The correct one (ref or rev) will be written into finalAttrs and
+ // it needs to be mutable for that.
+ Attrs finalAttrs(attrs);
+ auto type_ = maybeGetStrAttr(finalAttrs, "type");
+ if (type_ != type()) return {};
+
+ auto owner = getStrAttr(finalAttrs, "owner");
+ auto repo = getStrAttr(finalAttrs, "repo");
+
+ auto url = fmt("%s:%s/%s", *type_, owner, repo);
+ if (auto host = maybeGetStrAttr(finalAttrs, "host")) {
+ if (!std::regex_match(*host, hostRegex)) {
+ throw BadURL("URL '%s' contains an invalid instance host", url);
+ }
+ }
- for (auto & [name, value] : attrs)
- if (name != "type" && name != "owner" && name != "repo" && name != "ref" && name != "rev" && name != "narHash" && name != "lastModified" && name != "host")
- throw Error("unsupported input attribute '%s'", name);
+ if (auto refOrRev = maybeGetStrAttr(finalAttrs, "refOrRev")) {
+ finalAttrs.erase("refOrRev");
+ if (std::regex_match(*refOrRev, revRegex)) {
+ finalAttrs.emplace("rev", *refOrRev);
+ } else if (std::regex_match(*refOrRev, refRegex)) {
+ finalAttrs.emplace("ref", *refOrRev);
+ } else {
+ throw Error(
+ "in URL '%s', '%s' is not a commit hash or a branch/tag name",
+ url,
+ *refOrRev
+ );
+ }
+ } else if (auto ref = maybeGetStrAttr(finalAttrs, "ref")) {
+ if (!std::regex_match(*ref, refRegex)) {
+ throw BadURL("URL '%s' contains an invalid branch/tag name", url);
+ }
+ }
- getStrAttr(attrs, "owner");
- getStrAttr(attrs, "repo");
+ for (auto & [name, value] : finalAttrs) {
+ if (name != "type" && name != "owner" && name != "repo" && name != "ref" && name != "rev" && name != "narHash" && name != "lastModified" && name != "host") {
+ throw Error("unsupported input attribute '%s'", name);
+ }
+ }
Input input;
- input.attrs = attrs;
+ input.attrs = finalAttrs;
return input;
}
diff --git a/src/libfetchers/indirect.cc b/src/libfetchers/indirect.cc
index c73505b31..8c0176e84 100644
--- a/src/libfetchers/indirect.cc
+++ b/src/libfetchers/indirect.cc
@@ -17,6 +17,8 @@ struct IndirectInputScheme : InputScheme
std::optional<Hash> rev;
std::optional<std::string> ref;
+ Attrs attrs;
+
if (path.size() == 1) {
} else if (path.size() == 2) {
if (std::regex_match(path[1], revRegex))
@@ -26,29 +28,21 @@ struct IndirectInputScheme : InputScheme
else
throw BadURL("in flake URL '%s', '%s' is not a commit hash or branch/tag name", url.url, path[1]);
} else if (path.size() == 3) {
- if (!std::regex_match(path[1], refRegex))
- throw BadURL("in flake URL '%s', '%s' is not a branch/tag name", url.url, path[1]);
ref = path[1];
- if (!std::regex_match(path[2], revRegex))
- throw BadURL("in flake URL '%s', '%s' is not a commit hash", url.url, path[2]);
rev = Hash::parseAny(path[2], htSHA1);
} else
throw BadURL("GitHub URL '%s' is invalid", url.url);
std::string id = path[0];
- if (!std::regex_match(id, flakeRegex))
- throw BadURL("'%s' is not a valid flake ID", id);
- // FIXME: forbid query params?
+ attrs.emplace("type", "indirect");
+ attrs.emplace("id", id);
+ if (rev) attrs.emplace("rev", rev->gitRev());
+ if (ref) attrs.emplace("ref", *ref);
- Input input;
- input.direct = false;
- input.attrs.insert_or_assign("type", "indirect");
- input.attrs.insert_or_assign("id", id);
- if (rev) input.attrs.insert_or_assign("rev", rev->gitRev());
- if (ref) input.attrs.insert_or_assign("ref", *ref);
+ emplaceURLQueryIntoAttrs(url, attrs, {}, {});
- return input;
+ return inputFromAttrs(attrs);
}
std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
@@ -63,6 +57,18 @@ struct IndirectInputScheme : InputScheme
if (!std::regex_match(id, flakeRegex))
throw BadURL("'%s' is not a valid flake ID", id);
+ // TODO come up with a nicer error message for those two.
+ if (auto rev = maybeGetStrAttr(attrs, "rev")) {
+ if (!std::regex_match(*rev, revRegex)) {
+ throw BadURL("in flake '%s', '%s' is not a commit hash", id, *rev);
+ }
+ }
+ if (auto ref = maybeGetStrAttr(attrs, "ref")) {
+ if (!std::regex_match(*ref, refRegex)) {
+ throw BadURL("in flake '%s', '%s' is not a valid branch/tag name", id, *ref);
+ }
+ }
+
Input input;
input.direct = false;
input.attrs = attrs;
diff --git a/src/libfetchers/mercurial.cc b/src/libfetchers/mercurial.cc
index 23cf7b51d..b4150e9df 100644
--- a/src/libfetchers/mercurial.cc
+++ b/src/libfetchers/mercurial.cc
@@ -56,12 +56,7 @@ struct MercurialInputScheme : InputScheme
Attrs attrs;
attrs.emplace("type", "hg");
- for (auto &[name, value] : url.query) {
- if (name == "rev" || name == "ref")
- attrs.emplace(name, value);
- else
- url2.query.emplace(name, value);
- }
+ emplaceURLQueryIntoAttrs(url, attrs, {"revCount"}, {});
attrs.emplace("url", url2.to_string());
diff --git a/src/libfetchers/tarball.cc b/src/libfetchers/tarball.cc
index 6ce35aeb2..b11665805 100644
--- a/src/libfetchers/tarball.cc
+++ b/src/libfetchers/tarball.cc
@@ -201,29 +201,17 @@ struct CurlInputScheme : InputScheme
if (!isValidURL(_url, requireTree))
return std::nullopt;
- Input input;
-
auto url = _url;
- url.scheme = parseUrlScheme(url.scheme).transport;
-
- auto narHash = url.query.find("narHash");
- if (narHash != url.query.end())
- input.attrs.insert_or_assign("narHash", narHash->second);
+ Attrs attrs;
+ attrs.emplace("type", inputType());
- if (auto i = get(url.query, "rev"))
- input.attrs.insert_or_assign("rev", *i);
-
- if (auto i = get(url.query, "revCount"))
- if (auto n = string2Int<uint64_t>(*i))
- input.attrs.insert_or_assign("revCount", *n);
+ url.scheme = parseUrlScheme(url.scheme).transport;
- url.query.erase("rev");
- url.query.erase("revCount");
+ emplaceURLQueryIntoAttrs(url, attrs, {"revCount"}, {});
- input.attrs.insert_or_assign("type", inputType());
- input.attrs.insert_or_assign("url", url.to_string());
- return input;
+ attrs.emplace("url", url.to_string());
+ return inputFromAttrs(attrs);
}
std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
@@ -235,7 +223,7 @@ struct CurlInputScheme : InputScheme
std::set<std::string> allowedNames = {"type", "url", "narHash", "name", "unpack", "rev", "revCount", "lastModified"};
for (auto & [name, value] : attrs)
if (!allowedNames.count(name))
- throw Error("unsupported %s input attribute '%s'", *type, name);
+ throw Error("unsupported %s input attribute '%s'. If you wanted to fetch a tarball with a query parameter, please use '{ type = \"tarball\"; url = \"...\"; }'", *type, name);
Input input;
input.attrs = attrs;
diff --git a/src/libstore/build-result.cc b/src/libstore/build-result.cc
index 18f519c5c..de280c13f 100644
--- a/src/libstore/build-result.cc
+++ b/src/libstore/build-result.cc
@@ -15,4 +15,27 @@ GENERATE_CMP_EXT(
me->cpuUser,
me->cpuSystem);
+KeyedBuildResult BuildResult::restrictTo(DerivedPath path) const
+{
+ KeyedBuildResult res{*this, std::move(path)};
+
+ if (auto pbp = std::get_if<DerivedPath::Built>(&res.path)) {
+ auto & bp = *pbp;
+
+ /* Because goals are in general shared between derived paths
+ that share the same derivation, we need to filter their
+ results to get back just the results we care about.
+ */
+
+ for (auto it = res.builtOutputs.begin(); it != res.builtOutputs.end();) {
+ if (bp.outputs.contains(it->first))
+ ++it;
+ else
+ it = res.builtOutputs.erase(it);
+ }
+ }
+
+ return res;
+}
+
}
diff --git a/src/libstore/build-result.hh b/src/libstore/build-result.hh
index 8840fa7e3..9634fb944 100644
--- a/src/libstore/build-result.hh
+++ b/src/libstore/build-result.hh
@@ -11,6 +11,8 @@
namespace nix {
+struct KeyedBuildResult;
+
struct BuildResult
{
/**
@@ -112,6 +114,18 @@ struct BuildResult
{
throw Error("%s", errorMsg);
}
+
+ /**
+ * Project a BuildResult with just the information that pertains to
+ * the given path.
+ *
+ * A `BuildResult` may hold information for multiple derived paths;
+ * this function discards information about outputs not relevant in
+ * `path`. Build `Goal`s in particular may contain more outputs for
+ * a single build result than asked for directly, it's necessary to
+ * remove any such additional result to not leak other build infos.
+ */
+ KeyedBuildResult restrictTo(DerivedPath path) const;
};
/**
diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc
index 7cf8a55c9..38c54e854 100644
--- a/src/libstore/build/derivation-goal.cc
+++ b/src/libstore/build/derivation-goal.cc
@@ -79,7 +79,6 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath,
trace("created");
mcExpectedBuilds = std::make_unique<MaintainCount<uint64_t>>(worker.expectedBuilds);
- worker.updateProgress();
}
@@ -100,7 +99,6 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath, const BasicDerivation
trace("created");
mcExpectedBuilds = std::make_unique<MaintainCount<uint64_t>>(worker.expectedBuilds);
- worker.updateProgress();
/* Prevent the .chroot directory from being
garbage-collected. (See isActiveTempFile() in gc.cc.) */
@@ -670,7 +668,6 @@ void DerivationGoal::started()
act = std::make_unique<Activity>(*logger, lvlInfo, actBuild, msg,
Logger::Fields{worker.store.printStorePath(drvPath), hook ? machineName : "", 1, 1});
mcRunningBuilds = std::make_unique<MaintainCount<uint64_t>>(worker.runningBuilds);
- worker.updateProgress();
}
void DerivationGoal::tryToBuild()
@@ -932,7 +929,7 @@ void runPostBuildHook(
Finally const _wait([&] { proc.wait(); });
// FIXME just process the data, without a wrapper sink class
- proc.stdout()->drainInto(sink);
+ proc.getStdout()->drainInto(sink);
}
void DerivationGoal::buildDone()
@@ -1366,7 +1363,6 @@ void DerivationGoal::handleChildOutput(int fd, std::string_view data)
void DerivationGoal::handleEOF(int fd)
{
if (!currentLogLine.empty()) flushLine();
- worker.wakeUp(shared_from_this());
}
@@ -1537,8 +1533,6 @@ void DerivationGoal::done(
worker.failedBuilds++;
}
- worker.updateProgress();
-
auto traceBuiltOutputsFile = getEnv("_NIX_TRACE_BUILT_OUTPUTS").value_or("");
if (traceBuiltOutputsFile != "") {
std::fstream fs;
@@ -1566,7 +1560,7 @@ void DerivationGoal::waiteeDone(GoalPtr waitee, ExitCode result)
auto & outputs = nodeP->value;
for (auto & outputName : outputs) {
- auto buildResult = dg->getBuildResult(DerivedPath::Built {
+ auto buildResult = dg->buildResult.restrictTo(DerivedPath::Built {
.drvPath = makeConstantStorePathRef(dg->drvPath),
.outputs = OutputsSpec::Names { outputName },
});
diff --git a/src/libstore/build/drv-output-substitution-goal.cc b/src/libstore/build/drv-output-substitution-goal.cc
index 735a07f96..94c9206a3 100644
--- a/src/libstore/build/drv-output-substitution-goal.cc
+++ b/src/libstore/build/drv-output-substitution-goal.cc
@@ -41,14 +41,13 @@ void DrvOutputSubstitutionGoal::tryNext()
/* Make sure that we are allowed to start a substitution. Note that even
if maxSubstitutionJobs == 0, we still allow a substituter to run. This
prevents infinite waiting. */
- if (worker.runningCASubstitutions >= std::max(1U, settings.maxSubstitutionJobs.get())) {
+ if (worker.runningSubstitutions >= std::max(1U, settings.maxSubstitutionJobs.get())) {
worker.waitForBuildSlot(shared_from_this());
return;
}
maintainRunningSubstitutions =
- std::make_unique<MaintainCount<uint64_t>>(worker.runningCASubstitutions);
- worker.updateProgress();
+ std::make_unique<MaintainCount<uint64_t>>(worker.runningSubstitutions);
if (subs.size() == 0) {
/* None left. Terminate this goal and let someone else deal
@@ -62,7 +61,6 @@ void DrvOutputSubstitutionGoal::tryNext()
if (substituterFailed) {
worker.failedSubstitutions++;
- worker.updateProgress();
}
return;
@@ -164,10 +162,5 @@ void DrvOutputSubstitutionGoal::work()
(this->*state)();
}
-void DrvOutputSubstitutionGoal::handleEOF(int fd)
-{
- if (fd == downloadState->outPipe.readSide.get()) worker.wakeUp(shared_from_this());
-}
-
}
diff --git a/src/libstore/build/drv-output-substitution-goal.hh b/src/libstore/build/drv-output-substitution-goal.hh
index 008b211a1..598b119dc 100644
--- a/src/libstore/build/drv-output-substitution-goal.hh
+++ b/src/libstore/build/drv-output-substitution-goal.hh
@@ -4,7 +4,6 @@
#include "store-api.hh"
#include "goal.hh"
#include "realisation.hh"
-#include <thread>
#include <future>
namespace nix {
@@ -41,11 +40,6 @@ class DrvOutputSubstitutionGoal : public Goal {
*/
std::shared_ptr<Store> sub;
- /**
- * The substituter thread.
- */
- std::thread thr;
-
std::unique_ptr<MaintainCount<uint64_t>> maintainRunningSubstitutions;
struct DownloadState
@@ -78,7 +72,6 @@ public:
std::string key() override;
void work() override;
- void handleEOF(int fd) override;
JobCategory jobCategory() const override {
return JobCategory::Substitution;
diff --git a/src/libstore/build/entry-points.cc b/src/libstore/build/entry-points.cc
index d4bead28e..c6955600e 100644
--- a/src/libstore/build/entry-points.cc
+++ b/src/libstore/build/entry-points.cc
@@ -22,7 +22,7 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod
if (ex)
logError(i->ex->info());
else
- ex = std::move(i->ex);
+ ex = std::move(*i->ex);
}
if (i->exitCode != Goal::ecSuccess) {
if (auto i2 = dynamic_cast<DerivationGoal *>(i.get()))
@@ -62,10 +62,7 @@ std::vector<KeyedBuildResult> Store::buildPathsWithResults(
std::vector<KeyedBuildResult> results;
for (auto & [req, goalPtr] : state)
- results.emplace_back(KeyedBuildResult {
- goalPtr->getBuildResult(req),
- /* .path = */ req,
- });
+ results.emplace_back(goalPtr->buildResult.restrictTo(req));
return results;
}
@@ -78,7 +75,7 @@ BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivat
try {
worker.run(Goals{goal});
- return goal->getBuildResult(DerivedPath::Built {
+ return goal->buildResult.restrictTo(DerivedPath::Built {
.drvPath = makeConstantStorePathRef(drvPath),
.outputs = OutputsSpec::All {},
});
diff --git a/src/libstore/build/goal.cc b/src/libstore/build/goal.cc
index f8db98280..4db6af6e6 100644
--- a/src/libstore/build/goal.cc
+++ b/src/libstore/build/goal.cc
@@ -11,41 +11,10 @@ bool CompareGoalPtrs::operator() (const GoalPtr & a, const GoalPtr & b) const {
}
-BuildResult Goal::getBuildResult(const DerivedPath & req) const {
- BuildResult res { buildResult };
-
- if (auto pbp = std::get_if<DerivedPath::Built>(&req)) {
- auto & bp = *pbp;
-
- /* Because goals are in general shared between derived paths
- that share the same derivation, we need to filter their
- results to get back just the results we care about.
- */
-
- for (auto it = res.builtOutputs.begin(); it != res.builtOutputs.end();) {
- if (bp.outputs.contains(it->first))
- ++it;
- else
- it = res.builtOutputs.erase(it);
- }
- }
-
- return res;
-}
-
-
-void addToWeakGoals(WeakGoals & goals, GoalPtr p)
-{
- if (goals.find(p) != goals.end())
- return;
- goals.insert(p);
-}
-
-
void Goal::addWaitee(GoalPtr waitee)
{
waitees.insert(waitee);
- addToWeakGoals(waitee->waiters, shared_from_this());
+ waitee->waiters.insert(shared_from_this());
}
@@ -79,15 +48,14 @@ void Goal::waiteeDone(GoalPtr waitee, ExitCode result)
void Goal::amDone(ExitCode result, std::optional<Error> ex)
{
trace("done");
- assert(exitCode == ecBusy);
- assert(result == ecSuccess || result == ecFailed || result == ecNoSubstituters || result == ecIncompleteClosure);
+ assert(!exitCode.has_value());
exitCode = result;
if (ex) {
if (!waiters.empty())
logError(ex->info());
else
- this->ex = std::move(*ex);
+ this->ex = std::make_unique<Error>(std::move(*ex));
}
for (auto & i : waiters) {
diff --git a/src/libstore/build/goal.hh b/src/libstore/build/goal.hh
index 5b755c275..575621037 100644
--- a/src/libstore/build/goal.hh
+++ b/src/libstore/build/goal.hh
@@ -53,7 +53,7 @@ enum struct JobCategory {
struct Goal : public std::enable_shared_from_this<Goal>
{
- typedef enum {ecBusy, ecSuccess, ecFailed, ecNoSubstituters, ecIncompleteClosure} ExitCode;
+ typedef enum {ecSuccess, ecFailed, ecNoSubstituters, ecIncompleteClosure} ExitCode;
/**
* Backlink to the worker.
@@ -96,9 +96,8 @@ struct Goal : public std::enable_shared_from_this<Goal>
/**
* Whether the goal is finished.
*/
- ExitCode exitCode = ecBusy;
+ std::optional<ExitCode> exitCode;
-protected:
/**
* Build result.
*/
@@ -107,21 +106,9 @@ protected:
public:
/**
- * Project a `BuildResult` with just the information that pertains
- * to the given request.
- *
- * In general, goals may be aliased between multiple requests, and
- * the stored `BuildResult` has information for the union of all
- * requests. We don't want to leak what the other request are for
- * sake of both privacy and determinism, and this "safe accessor"
- * ensures we don't.
- */
- BuildResult getBuildResult(const DerivedPath &) const;
-
- /**
* Exception containing an error message, if any.
*/
- std::optional<Error> ex;
+ std::unique_ptr<Error> ex;
Goal(Worker & worker, DerivedPath path)
: worker(worker)
@@ -145,7 +132,6 @@ public:
virtual void handleEOF(int fd)
{
- abort();
}
void trace(std::string_view s);
@@ -175,6 +161,4 @@ public:
virtual JobCategory jobCategory() const = 0;
};
-void addToWeakGoals(WeakGoals & goals, GoalPtr p);
-
}
diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc
index 660512e49..2f1f338c1 100644
--- a/src/libstore/build/local-derivation-goal.cc
+++ b/src/libstore/build/local-derivation-goal.cc
@@ -45,7 +45,6 @@
#include <sys/prctl.h>
#include <sys/syscall.h>
#if HAVE_SECCOMP
-#include "linux/fchmodat2-compat.hh"
#include <seccomp.h>
#endif
#define pivot_root(new_root, put_old) (syscall(SYS_pivot_root, new_root, put_old))
@@ -976,7 +975,7 @@ bool LocalDerivationGoal::isAllowed(const DerivedPath & req)
struct RestrictedStoreConfig : virtual LocalFSStoreConfig
{
using LocalFSStoreConfig::LocalFSStoreConfig;
- const std::string name() { return "Restricted Store"; }
+ const std::string name() override { return "Restricted Store"; }
};
/* A wrapper around LocalStore that only allows building/querying of
@@ -1363,6 +1362,20 @@ void LocalDerivationGoal::chownToBuilder(const Path & path)
throw SysError("cannot change ownership of '%1%'", path);
}
+#if HAVE_SECCOMP
+
+static void allowSyscall(scmp_filter_ctx ctx, int syscall) {
+ if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, syscall, 0) != 0)
+ throw SysError("unable to add seccomp rule");
+}
+
+#define ALLOW_CHMOD_IF_SAFE(ctx, syscall, modePos) \
+ if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, syscall, 1, SCMP_A##modePos(SCMP_CMP_MASKED_EQ, S_ISUID | S_ISGID, 0)) != 0 || \
+ seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), syscall, 1, SCMP_A##modePos(SCMP_CMP_MASKED_EQ, S_ISUID, S_ISUID)) != 0 || \
+ seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), syscall, 1, SCMP_A##modePos(SCMP_CMP_MASKED_EQ, S_ISGID, S_ISGID)) != 0) \
+ throw SysError("unable to add seccomp rule");
+
+#endif
void setupSeccomp()
{
@@ -1370,7 +1383,9 @@ void setupSeccomp()
#if HAVE_SECCOMP
scmp_filter_ctx ctx;
- if (!(ctx = seccomp_init(SCMP_ACT_ALLOW)))
+ // Pretend that syscalls we don't yet know about don't exist.
+ // This is the best option for compatibility: after all, they did in fact not exist not too long ago.
+ if (!(ctx = seccomp_init(SCMP_ACT_ERRNO(ENOSYS))))
throw SysError("unable to initialize seccomp mode 2");
Finally cleanup([&]() {
@@ -1405,28 +1420,514 @@ void setupSeccomp()
seccomp_arch_add(ctx, SCMP_ARCH_MIPSEL64N32) != 0)
printError("unable to add mips64el-*abin32 seccomp architecture");
- /* Prevent builders from creating setuid/setgid binaries. */
- for (int perm : { S_ISUID, S_ISGID }) {
- if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(chmod), 1,
- SCMP_A1(SCMP_CMP_MASKED_EQ, (scmp_datum_t) perm, (scmp_datum_t) perm)) != 0)
- throw SysError("unable to add seccomp rule");
-
- if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(fchmod), 1,
- SCMP_A1(SCMP_CMP_MASKED_EQ, (scmp_datum_t) perm, (scmp_datum_t) perm)) != 0)
- throw SysError("unable to add seccomp rule");
-
- if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(fchmodat), 1,
- SCMP_A2(SCMP_CMP_MASKED_EQ, (scmp_datum_t) perm, (scmp_datum_t) perm)) != 0)
- throw SysError("unable to add seccomp rule");
-
- if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), NIX_SYSCALL_FCHMODAT2, 1,
- SCMP_A2(SCMP_CMP_MASKED_EQ, (scmp_datum_t) perm, (scmp_datum_t) perm)) != 0)
- throw SysError("unable to add seccomp rule");
- }
-
- /* Prevent builders from creating EAs or ACLs. Not all filesystems
- support these, and they're not allowed in the Nix store because
- they're not representable in the NAR serialisation. */
+ // This list is intended for machine consumption.
+ // Please keep its format, order and BEGIN/END markers.
+ //
+ // Currently, it is up to date with libseccomp 2.5.5 and glibc 2.39.
+ // Run check-syscalls to determine which new syscalls should be added.
+ // New syscalls must be audited and handled in a way that blocks the following dangerous operations:
+ // * Creation of non-empty setuid/setgid files
+ // * Creation of extended attributes (including ACLs)
+ //
+ // BEGIN extract-syscalls
+ allowSyscall(ctx, SCMP_SYS(accept));
+ allowSyscall(ctx, SCMP_SYS(accept4));
+ allowSyscall(ctx, SCMP_SYS(access));
+ allowSyscall(ctx, SCMP_SYS(acct));
+ allowSyscall(ctx, SCMP_SYS(add_key));
+ allowSyscall(ctx, SCMP_SYS(adjtimex));
+ allowSyscall(ctx, SCMP_SYS(afs_syscall));
+ allowSyscall(ctx, SCMP_SYS(alarm));
+ allowSyscall(ctx, SCMP_SYS(arch_prctl));
+ allowSyscall(ctx, SCMP_SYS(arm_fadvise64_64));
+ allowSyscall(ctx, SCMP_SYS(arm_sync_file_range));
+ allowSyscall(ctx, SCMP_SYS(bdflush));
+ allowSyscall(ctx, SCMP_SYS(bind));
+ allowSyscall(ctx, SCMP_SYS(bpf));
+ allowSyscall(ctx, SCMP_SYS(break));
+ allowSyscall(ctx, SCMP_SYS(breakpoint));
+ allowSyscall(ctx, SCMP_SYS(brk));
+ allowSyscall(ctx, SCMP_SYS(cachectl));
+ allowSyscall(ctx, SCMP_SYS(cacheflush));
+ allowSyscall(ctx, SCMP_SYS(cachestat));
+ allowSyscall(ctx, SCMP_SYS(capget));
+ allowSyscall(ctx, SCMP_SYS(capset));
+ allowSyscall(ctx, SCMP_SYS(chdir));
+ // skip chmod (dangerous)
+ allowSyscall(ctx, SCMP_SYS(chown));
+ allowSyscall(ctx, SCMP_SYS(chown32));
+ allowSyscall(ctx, SCMP_SYS(chroot));
+ allowSyscall(ctx, SCMP_SYS(clock_adjtime));
+ allowSyscall(ctx, SCMP_SYS(clock_adjtime64));
+ allowSyscall(ctx, SCMP_SYS(clock_getres));
+ allowSyscall(ctx, SCMP_SYS(clock_getres_time64));
+ allowSyscall(ctx, SCMP_SYS(clock_gettime));
+ allowSyscall(ctx, SCMP_SYS(clock_gettime64));
+ allowSyscall(ctx, SCMP_SYS(clock_nanosleep));
+ allowSyscall(ctx, SCMP_SYS(clock_nanosleep_time64));
+ allowSyscall(ctx, SCMP_SYS(clock_settime));
+ allowSyscall(ctx, SCMP_SYS(clock_settime64));
+ allowSyscall(ctx, SCMP_SYS(clone));
+ allowSyscall(ctx, SCMP_SYS(clone3));
+ allowSyscall(ctx, SCMP_SYS(close));
+ allowSyscall(ctx, SCMP_SYS(close_range));
+ allowSyscall(ctx, SCMP_SYS(connect));
+ allowSyscall(ctx, SCMP_SYS(copy_file_range));
+ allowSyscall(ctx, SCMP_SYS(creat));
+ allowSyscall(ctx, SCMP_SYS(create_module));
+ allowSyscall(ctx, SCMP_SYS(delete_module));
+ allowSyscall(ctx, SCMP_SYS(dup));
+ allowSyscall(ctx, SCMP_SYS(dup2));
+ allowSyscall(ctx, SCMP_SYS(dup3));
+ allowSyscall(ctx, SCMP_SYS(epoll_create));
+ allowSyscall(ctx, SCMP_SYS(epoll_create1));
+ allowSyscall(ctx, SCMP_SYS(epoll_ctl));
+ allowSyscall(ctx, SCMP_SYS(epoll_ctl_old));
+ allowSyscall(ctx, SCMP_SYS(epoll_pwait));
+ allowSyscall(ctx, SCMP_SYS(epoll_pwait2));
+ allowSyscall(ctx, SCMP_SYS(epoll_wait));
+ allowSyscall(ctx, SCMP_SYS(epoll_wait_old));
+ allowSyscall(ctx, SCMP_SYS(eventfd));
+ allowSyscall(ctx, SCMP_SYS(eventfd2));
+ allowSyscall(ctx, SCMP_SYS(execve));
+ allowSyscall(ctx, SCMP_SYS(execveat));
+ allowSyscall(ctx, SCMP_SYS(exit));
+ allowSyscall(ctx, SCMP_SYS(exit_group));
+ allowSyscall(ctx, SCMP_SYS(faccessat));
+ allowSyscall(ctx, SCMP_SYS(faccessat2));
+ allowSyscall(ctx, SCMP_SYS(fadvise64));
+ allowSyscall(ctx, SCMP_SYS(fadvise64_64));
+ allowSyscall(ctx, SCMP_SYS(fallocate));
+ allowSyscall(ctx, SCMP_SYS(fanotify_init));
+ allowSyscall(ctx, SCMP_SYS(fanotify_mark));
+ allowSyscall(ctx, SCMP_SYS(fchdir));
+ // skip fchmod (dangerous)
+ // skip fchmodat (dangerous)
+ // skip fchmodat2 (dangerous)
+ allowSyscall(ctx, SCMP_SYS(fchown));
+ allowSyscall(ctx, SCMP_SYS(fchown32));
+ allowSyscall(ctx, SCMP_SYS(fchownat));
+ allowSyscall(ctx, SCMP_SYS(fcntl));
+ allowSyscall(ctx, SCMP_SYS(fcntl64));
+ allowSyscall(ctx, SCMP_SYS(fdatasync));
+ allowSyscall(ctx, SCMP_SYS(fgetxattr));
+ allowSyscall(ctx, SCMP_SYS(finit_module));
+ allowSyscall(ctx, SCMP_SYS(flistxattr));
+ allowSyscall(ctx, SCMP_SYS(flock));
+ allowSyscall(ctx, SCMP_SYS(fork));
+ allowSyscall(ctx, SCMP_SYS(fremovexattr));
+ allowSyscall(ctx, SCMP_SYS(fsconfig));
+ // skip fsetxattr (dangerous)
+ allowSyscall(ctx, SCMP_SYS(fsmount));
+ allowSyscall(ctx, SCMP_SYS(fsopen));
+ allowSyscall(ctx, SCMP_SYS(fspick));
+ allowSyscall(ctx, SCMP_SYS(fstat));
+ allowSyscall(ctx, SCMP_SYS(fstat64));
+ allowSyscall(ctx, SCMP_SYS(fstatat64));
+ allowSyscall(ctx, SCMP_SYS(fstatfs));
+ allowSyscall(ctx, SCMP_SYS(fstatfs64));
+ allowSyscall(ctx, SCMP_SYS(fsync));
+ allowSyscall(ctx, SCMP_SYS(ftime));
+ allowSyscall(ctx, SCMP_SYS(ftruncate));
+ allowSyscall(ctx, SCMP_SYS(ftruncate64));
+ allowSyscall(ctx, SCMP_SYS(futex));
+ allowSyscall(ctx, SCMP_SYS(futex_requeue));
+ allowSyscall(ctx, SCMP_SYS(futex_time64));
+ allowSyscall(ctx, SCMP_SYS(futex_wait));
+ allowSyscall(ctx, SCMP_SYS(futex_waitv));
+ allowSyscall(ctx, SCMP_SYS(futex_wake));
+ allowSyscall(ctx, SCMP_SYS(futimesat));
+ allowSyscall(ctx, SCMP_SYS(getcpu));
+ allowSyscall(ctx, SCMP_SYS(getcwd));
+ allowSyscall(ctx, SCMP_SYS(getdents));
+ allowSyscall(ctx, SCMP_SYS(getdents64));
+ allowSyscall(ctx, SCMP_SYS(getegid));
+ allowSyscall(ctx, SCMP_SYS(getegid32));
+ allowSyscall(ctx, SCMP_SYS(geteuid));
+ allowSyscall(ctx, SCMP_SYS(geteuid32));
+ allowSyscall(ctx, SCMP_SYS(getgid));
+ allowSyscall(ctx, SCMP_SYS(getgid32));
+ allowSyscall(ctx, SCMP_SYS(getgroups));
+ allowSyscall(ctx, SCMP_SYS(getgroups32));
+ allowSyscall(ctx, SCMP_SYS(getitimer));
+ allowSyscall(ctx, SCMP_SYS(get_kernel_syms));
+ allowSyscall(ctx, SCMP_SYS(get_mempolicy));
+ allowSyscall(ctx, SCMP_SYS(getpeername));
+ allowSyscall(ctx, SCMP_SYS(getpgid));
+ allowSyscall(ctx, SCMP_SYS(getpgrp));
+ allowSyscall(ctx, SCMP_SYS(getpid));
+ allowSyscall(ctx, SCMP_SYS(getpmsg));
+ allowSyscall(ctx, SCMP_SYS(getppid));
+ allowSyscall(ctx, SCMP_SYS(getpriority));
+ allowSyscall(ctx, SCMP_SYS(getrandom));
+ allowSyscall(ctx, SCMP_SYS(getresgid));
+ allowSyscall(ctx, SCMP_SYS(getresgid32));
+ allowSyscall(ctx, SCMP_SYS(getresuid));
+ allowSyscall(ctx, SCMP_SYS(getresuid32));
+ allowSyscall(ctx, SCMP_SYS(getrlimit));
+ allowSyscall(ctx, SCMP_SYS(get_robust_list));
+ allowSyscall(ctx, SCMP_SYS(getrusage));
+ allowSyscall(ctx, SCMP_SYS(getsid));
+ allowSyscall(ctx, SCMP_SYS(getsockname));
+ allowSyscall(ctx, SCMP_SYS(getsockopt));
+ allowSyscall(ctx, SCMP_SYS(get_thread_area));
+ allowSyscall(ctx, SCMP_SYS(gettid));
+ allowSyscall(ctx, SCMP_SYS(gettimeofday));
+ allowSyscall(ctx, SCMP_SYS(get_tls));
+ allowSyscall(ctx, SCMP_SYS(getuid));
+ allowSyscall(ctx, SCMP_SYS(getuid32));
+ allowSyscall(ctx, SCMP_SYS(getxattr));
+ allowSyscall(ctx, SCMP_SYS(gtty));
+ allowSyscall(ctx, SCMP_SYS(idle));
+ allowSyscall(ctx, SCMP_SYS(init_module));
+ allowSyscall(ctx, SCMP_SYS(inotify_add_watch));
+ allowSyscall(ctx, SCMP_SYS(inotify_init));
+ allowSyscall(ctx, SCMP_SYS(inotify_init1));
+ allowSyscall(ctx, SCMP_SYS(inotify_rm_watch));
+ allowSyscall(ctx, SCMP_SYS(io_cancel));
+ allowSyscall(ctx, SCMP_SYS(ioctl));
+ allowSyscall(ctx, SCMP_SYS(io_destroy));
+ allowSyscall(ctx, SCMP_SYS(io_getevents));
+ allowSyscall(ctx, SCMP_SYS(ioperm));
+ allowSyscall(ctx, SCMP_SYS(io_pgetevents));
+ allowSyscall(ctx, SCMP_SYS(io_pgetevents_time64));
+ allowSyscall(ctx, SCMP_SYS(iopl));
+ allowSyscall(ctx, SCMP_SYS(ioprio_get));
+ allowSyscall(ctx, SCMP_SYS(ioprio_set));
+ allowSyscall(ctx, SCMP_SYS(io_setup));
+ allowSyscall(ctx, SCMP_SYS(io_submit));
+ // skip io_uring_enter (may become dangerous)
+ // skip io_uring_register (may become dangerous)
+ // skip io_uring_setup (may become dangerous)
+ allowSyscall(ctx, SCMP_SYS(ipc));
+ allowSyscall(ctx, SCMP_SYS(kcmp));
+ allowSyscall(ctx, SCMP_SYS(kexec_file_load));
+ allowSyscall(ctx, SCMP_SYS(kexec_load));
+ allowSyscall(ctx, SCMP_SYS(keyctl));
+ allowSyscall(ctx, SCMP_SYS(kill));
+ allowSyscall(ctx, SCMP_SYS(landlock_add_rule));
+ allowSyscall(ctx, SCMP_SYS(landlock_create_ruleset));
+ allowSyscall(ctx, SCMP_SYS(landlock_restrict_self));
+ allowSyscall(ctx, SCMP_SYS(lchown));
+ allowSyscall(ctx, SCMP_SYS(lchown32));
+ allowSyscall(ctx, SCMP_SYS(lgetxattr));
+ allowSyscall(ctx, SCMP_SYS(link));
+ allowSyscall(ctx, SCMP_SYS(linkat));
+ allowSyscall(ctx, SCMP_SYS(listen));
+ allowSyscall(ctx, SCMP_SYS(listxattr));
+ allowSyscall(ctx, SCMP_SYS(llistxattr));
+ allowSyscall(ctx, SCMP_SYS(_llseek));
+ allowSyscall(ctx, SCMP_SYS(lock));
+ allowSyscall(ctx, SCMP_SYS(lookup_dcookie));
+ allowSyscall(ctx, SCMP_SYS(lremovexattr));
+ allowSyscall(ctx, SCMP_SYS(lseek));
+ // skip lsetxattr (dangerous)
+ allowSyscall(ctx, SCMP_SYS(lstat));
+ allowSyscall(ctx, SCMP_SYS(lstat64));
+ allowSyscall(ctx, SCMP_SYS(madvise));
+ allowSyscall(ctx, SCMP_SYS(map_shadow_stack));
+ allowSyscall(ctx, SCMP_SYS(mbind));
+ allowSyscall(ctx, SCMP_SYS(membarrier));
+ allowSyscall(ctx, SCMP_SYS(memfd_create));
+ allowSyscall(ctx, SCMP_SYS(memfd_secret));
+ allowSyscall(ctx, SCMP_SYS(migrate_pages));
+ allowSyscall(ctx, SCMP_SYS(mincore));
+ allowSyscall(ctx, SCMP_SYS(mkdir));
+ allowSyscall(ctx, SCMP_SYS(mkdirat));
+ allowSyscall(ctx, SCMP_SYS(mknod));
+ allowSyscall(ctx, SCMP_SYS(mknodat));
+ allowSyscall(ctx, SCMP_SYS(mlock));
+ allowSyscall(ctx, SCMP_SYS(mlock2));
+ allowSyscall(ctx, SCMP_SYS(mlockall));
+ allowSyscall(ctx, SCMP_SYS(mmap));
+ allowSyscall(ctx, SCMP_SYS(mmap2));
+ allowSyscall(ctx, SCMP_SYS(modify_ldt));
+ allowSyscall(ctx, SCMP_SYS(mount));
+ allowSyscall(ctx, SCMP_SYS(mount_setattr));
+ allowSyscall(ctx, SCMP_SYS(move_mount));
+ allowSyscall(ctx, SCMP_SYS(move_pages));
+ allowSyscall(ctx, SCMP_SYS(mprotect));
+ allowSyscall(ctx, SCMP_SYS(mpx));
+ allowSyscall(ctx, SCMP_SYS(mq_getsetattr));
+ allowSyscall(ctx, SCMP_SYS(mq_notify));
+ allowSyscall(ctx, SCMP_SYS(mq_open));
+ allowSyscall(ctx, SCMP_SYS(mq_timedreceive));
+ allowSyscall(ctx, SCMP_SYS(mq_timedreceive_time64));
+ allowSyscall(ctx, SCMP_SYS(mq_timedsend));
+ allowSyscall(ctx, SCMP_SYS(mq_timedsend_time64));
+ allowSyscall(ctx, SCMP_SYS(mq_unlink));
+ allowSyscall(ctx, SCMP_SYS(mremap));
+ allowSyscall(ctx, SCMP_SYS(msgctl));
+ allowSyscall(ctx, SCMP_SYS(msgget));
+ allowSyscall(ctx, SCMP_SYS(msgrcv));
+ allowSyscall(ctx, SCMP_SYS(msgsnd));
+ allowSyscall(ctx, SCMP_SYS(msync));
+ allowSyscall(ctx, SCMP_SYS(multiplexer));
+ allowSyscall(ctx, SCMP_SYS(munlock));
+ allowSyscall(ctx, SCMP_SYS(munlockall));
+ allowSyscall(ctx, SCMP_SYS(munmap));
+ allowSyscall(ctx, SCMP_SYS(name_to_handle_at));
+ allowSyscall(ctx, SCMP_SYS(nanosleep));
+ allowSyscall(ctx, SCMP_SYS(newfstatat));
+ allowSyscall(ctx, SCMP_SYS(_newselect));
+ allowSyscall(ctx, SCMP_SYS(nfsservctl));
+ allowSyscall(ctx, SCMP_SYS(nice));
+ allowSyscall(ctx, SCMP_SYS(oldfstat));
+ allowSyscall(ctx, SCMP_SYS(oldlstat));
+ allowSyscall(ctx, SCMP_SYS(oldolduname));
+ allowSyscall(ctx, SCMP_SYS(oldstat));
+ allowSyscall(ctx, SCMP_SYS(olduname));
+ allowSyscall(ctx, SCMP_SYS(open));
+ allowSyscall(ctx, SCMP_SYS(openat));
+ allowSyscall(ctx, SCMP_SYS(openat2));
+ allowSyscall(ctx, SCMP_SYS(open_by_handle_at));
+ allowSyscall(ctx, SCMP_SYS(open_tree));
+ allowSyscall(ctx, SCMP_SYS(pause));
+ allowSyscall(ctx, SCMP_SYS(pciconfig_iobase));
+ allowSyscall(ctx, SCMP_SYS(pciconfig_read));
+ allowSyscall(ctx, SCMP_SYS(pciconfig_write));
+ allowSyscall(ctx, SCMP_SYS(perf_event_open));
+ allowSyscall(ctx, SCMP_SYS(personality));
+ allowSyscall(ctx, SCMP_SYS(pidfd_getfd));
+ allowSyscall(ctx, SCMP_SYS(pidfd_open));
+ allowSyscall(ctx, SCMP_SYS(pidfd_send_signal));
+ allowSyscall(ctx, SCMP_SYS(pipe));
+ allowSyscall(ctx, SCMP_SYS(pipe2));
+ allowSyscall(ctx, SCMP_SYS(pivot_root));
+ allowSyscall(ctx, SCMP_SYS(pkey_alloc));
+ allowSyscall(ctx, SCMP_SYS(pkey_free));
+ allowSyscall(ctx, SCMP_SYS(pkey_mprotect));
+ allowSyscall(ctx, SCMP_SYS(poll));
+ allowSyscall(ctx, SCMP_SYS(ppoll));
+ allowSyscall(ctx, SCMP_SYS(ppoll_time64));
+ allowSyscall(ctx, SCMP_SYS(prctl));
+ allowSyscall(ctx, SCMP_SYS(pread64));
+ allowSyscall(ctx, SCMP_SYS(preadv));
+ allowSyscall(ctx, SCMP_SYS(preadv2));
+ allowSyscall(ctx, SCMP_SYS(prlimit64));
+ allowSyscall(ctx, SCMP_SYS(process_madvise));
+ allowSyscall(ctx, SCMP_SYS(process_mrelease));
+ allowSyscall(ctx, SCMP_SYS(process_vm_readv));
+ allowSyscall(ctx, SCMP_SYS(process_vm_writev));
+ allowSyscall(ctx, SCMP_SYS(prof));
+ allowSyscall(ctx, SCMP_SYS(profil));
+ allowSyscall(ctx, SCMP_SYS(pselect6));
+ allowSyscall(ctx, SCMP_SYS(pselect6_time64));
+ allowSyscall(ctx, SCMP_SYS(ptrace));
+ allowSyscall(ctx, SCMP_SYS(putpmsg));
+ allowSyscall(ctx, SCMP_SYS(pwrite64));
+ allowSyscall(ctx, SCMP_SYS(pwritev));
+ allowSyscall(ctx, SCMP_SYS(pwritev2));
+ allowSyscall(ctx, SCMP_SYS(query_module));
+ allowSyscall(ctx, SCMP_SYS(quotactl));
+ allowSyscall(ctx, SCMP_SYS(quotactl_fd));
+ allowSyscall(ctx, SCMP_SYS(read));
+ allowSyscall(ctx, SCMP_SYS(readahead));
+ allowSyscall(ctx, SCMP_SYS(readdir));
+ allowSyscall(ctx, SCMP_SYS(readlink));
+ allowSyscall(ctx, SCMP_SYS(readlinkat));
+ allowSyscall(ctx, SCMP_SYS(readv));
+ allowSyscall(ctx, SCMP_SYS(reboot));
+ allowSyscall(ctx, SCMP_SYS(recv));
+ allowSyscall(ctx, SCMP_SYS(recvfrom));
+ allowSyscall(ctx, SCMP_SYS(recvmmsg));
+ allowSyscall(ctx, SCMP_SYS(recvmmsg_time64));
+ allowSyscall(ctx, SCMP_SYS(recvmsg));
+ allowSyscall(ctx, SCMP_SYS(remap_file_pages));
+ allowSyscall(ctx, SCMP_SYS(removexattr));
+ allowSyscall(ctx, SCMP_SYS(rename));
+ allowSyscall(ctx, SCMP_SYS(renameat));
+ allowSyscall(ctx, SCMP_SYS(renameat2));
+ allowSyscall(ctx, SCMP_SYS(request_key));
+ allowSyscall(ctx, SCMP_SYS(restart_syscall));
+ allowSyscall(ctx, SCMP_SYS(riscv_flush_icache));
+ allowSyscall(ctx, SCMP_SYS(rmdir));
+ allowSyscall(ctx, SCMP_SYS(rseq));
+ allowSyscall(ctx, SCMP_SYS(rtas));
+ allowSyscall(ctx, SCMP_SYS(rt_sigaction));
+ allowSyscall(ctx, SCMP_SYS(rt_sigpending));
+ allowSyscall(ctx, SCMP_SYS(rt_sigprocmask));
+ allowSyscall(ctx, SCMP_SYS(rt_sigqueueinfo));
+ allowSyscall(ctx, SCMP_SYS(rt_sigreturn));
+ allowSyscall(ctx, SCMP_SYS(rt_sigsuspend));
+ allowSyscall(ctx, SCMP_SYS(rt_sigtimedwait));
+ allowSyscall(ctx, SCMP_SYS(rt_sigtimedwait_time64));
+ allowSyscall(ctx, SCMP_SYS(rt_tgsigqueueinfo));
+ allowSyscall(ctx, SCMP_SYS(s390_guarded_storage));
+ allowSyscall(ctx, SCMP_SYS(s390_pci_mmio_read));
+ allowSyscall(ctx, SCMP_SYS(s390_pci_mmio_write));
+ allowSyscall(ctx, SCMP_SYS(s390_runtime_instr));
+ allowSyscall(ctx, SCMP_SYS(s390_sthyi));
+ allowSyscall(ctx, SCMP_SYS(sched_getaffinity));
+ allowSyscall(ctx, SCMP_SYS(sched_getattr));
+ allowSyscall(ctx, SCMP_SYS(sched_getparam));
+ allowSyscall(ctx, SCMP_SYS(sched_get_priority_max));
+ allowSyscall(ctx, SCMP_SYS(sched_get_priority_min));
+ allowSyscall(ctx, SCMP_SYS(sched_getscheduler));
+ allowSyscall(ctx, SCMP_SYS(sched_rr_get_interval));
+ allowSyscall(ctx, SCMP_SYS(sched_rr_get_interval_time64));
+ allowSyscall(ctx, SCMP_SYS(sched_setaffinity));
+ allowSyscall(ctx, SCMP_SYS(sched_setattr));
+ allowSyscall(ctx, SCMP_SYS(sched_setparam));
+ allowSyscall(ctx, SCMP_SYS(sched_setscheduler));
+ allowSyscall(ctx, SCMP_SYS(sched_yield));
+ allowSyscall(ctx, SCMP_SYS(seccomp));
+ allowSyscall(ctx, SCMP_SYS(security));
+ allowSyscall(ctx, SCMP_SYS(select));
+ allowSyscall(ctx, SCMP_SYS(semctl));
+ allowSyscall(ctx, SCMP_SYS(semget));
+ allowSyscall(ctx, SCMP_SYS(semop));
+ allowSyscall(ctx, SCMP_SYS(semtimedop));
+ allowSyscall(ctx, SCMP_SYS(semtimedop_time64));
+ allowSyscall(ctx, SCMP_SYS(send));
+ allowSyscall(ctx, SCMP_SYS(sendfile));
+ allowSyscall(ctx, SCMP_SYS(sendfile64));
+ allowSyscall(ctx, SCMP_SYS(sendmmsg));
+ allowSyscall(ctx, SCMP_SYS(sendmsg));
+ allowSyscall(ctx, SCMP_SYS(sendto));
+ allowSyscall(ctx, SCMP_SYS(setdomainname));
+ allowSyscall(ctx, SCMP_SYS(setfsgid));
+ allowSyscall(ctx, SCMP_SYS(setfsgid32));
+ allowSyscall(ctx, SCMP_SYS(setfsuid));
+ allowSyscall(ctx, SCMP_SYS(setfsuid32));
+ allowSyscall(ctx, SCMP_SYS(setgid));
+ allowSyscall(ctx, SCMP_SYS(setgid32));
+ allowSyscall(ctx, SCMP_SYS(setgroups));
+ allowSyscall(ctx, SCMP_SYS(setgroups32));
+ allowSyscall(ctx, SCMP_SYS(sethostname));
+ allowSyscall(ctx, SCMP_SYS(setitimer));
+ allowSyscall(ctx, SCMP_SYS(set_mempolicy));
+ allowSyscall(ctx, SCMP_SYS(set_mempolicy_home_node));
+ allowSyscall(ctx, SCMP_SYS(setns));
+ allowSyscall(ctx, SCMP_SYS(setpgid));
+ allowSyscall(ctx, SCMP_SYS(setpriority));
+ allowSyscall(ctx, SCMP_SYS(setregid));
+ allowSyscall(ctx, SCMP_SYS(setregid32));
+ allowSyscall(ctx, SCMP_SYS(setresgid));
+ allowSyscall(ctx, SCMP_SYS(setresgid32));
+ allowSyscall(ctx, SCMP_SYS(setresuid));
+ allowSyscall(ctx, SCMP_SYS(setresuid32));
+ allowSyscall(ctx, SCMP_SYS(setreuid));
+ allowSyscall(ctx, SCMP_SYS(setreuid32));
+ allowSyscall(ctx, SCMP_SYS(setrlimit));
+ allowSyscall(ctx, SCMP_SYS(set_robust_list));
+ allowSyscall(ctx, SCMP_SYS(setsid));
+ allowSyscall(ctx, SCMP_SYS(setsockopt));
+ allowSyscall(ctx, SCMP_SYS(set_thread_area));
+ allowSyscall(ctx, SCMP_SYS(set_tid_address));
+ allowSyscall(ctx, SCMP_SYS(settimeofday));
+ allowSyscall(ctx, SCMP_SYS(set_tls));
+ allowSyscall(ctx, SCMP_SYS(setuid));
+ allowSyscall(ctx, SCMP_SYS(setuid32));
+ // skip setxattr (dangerous)
+ allowSyscall(ctx, SCMP_SYS(sgetmask));
+ allowSyscall(ctx, SCMP_SYS(shmat));
+ allowSyscall(ctx, SCMP_SYS(shmctl));
+ allowSyscall(ctx, SCMP_SYS(shmdt));
+ allowSyscall(ctx, SCMP_SYS(shmget));
+ allowSyscall(ctx, SCMP_SYS(shutdown));
+ allowSyscall(ctx, SCMP_SYS(sigaction));
+ allowSyscall(ctx, SCMP_SYS(sigaltstack));
+ allowSyscall(ctx, SCMP_SYS(signal));
+ allowSyscall(ctx, SCMP_SYS(signalfd));
+ allowSyscall(ctx, SCMP_SYS(signalfd4));
+ allowSyscall(ctx, SCMP_SYS(sigpending));
+ allowSyscall(ctx, SCMP_SYS(sigprocmask));
+ allowSyscall(ctx, SCMP_SYS(sigreturn));
+ allowSyscall(ctx, SCMP_SYS(sigsuspend));
+ allowSyscall(ctx, SCMP_SYS(socket));
+ allowSyscall(ctx, SCMP_SYS(socketcall));
+ allowSyscall(ctx, SCMP_SYS(socketpair));
+ allowSyscall(ctx, SCMP_SYS(splice));
+ allowSyscall(ctx, SCMP_SYS(spu_create));
+ allowSyscall(ctx, SCMP_SYS(spu_run));
+ allowSyscall(ctx, SCMP_SYS(ssetmask));
+ allowSyscall(ctx, SCMP_SYS(stat));
+ allowSyscall(ctx, SCMP_SYS(stat64));
+ allowSyscall(ctx, SCMP_SYS(statfs));
+ allowSyscall(ctx, SCMP_SYS(statfs64));
+ allowSyscall(ctx, SCMP_SYS(statx));
+ allowSyscall(ctx, SCMP_SYS(stime));
+ allowSyscall(ctx, SCMP_SYS(stty));
+ allowSyscall(ctx, SCMP_SYS(subpage_prot));
+ allowSyscall(ctx, SCMP_SYS(swapcontext));
+ allowSyscall(ctx, SCMP_SYS(swapoff));
+ allowSyscall(ctx, SCMP_SYS(swapon));
+ allowSyscall(ctx, SCMP_SYS(switch_endian));
+ allowSyscall(ctx, SCMP_SYS(symlink));
+ allowSyscall(ctx, SCMP_SYS(symlinkat));
+ allowSyscall(ctx, SCMP_SYS(sync));
+ allowSyscall(ctx, SCMP_SYS(sync_file_range));
+ allowSyscall(ctx, SCMP_SYS(sync_file_range2));
+ allowSyscall(ctx, SCMP_SYS(syncfs));
+ allowSyscall(ctx, SCMP_SYS(syscall));
+ allowSyscall(ctx, SCMP_SYS(_sysctl));
+ allowSyscall(ctx, SCMP_SYS(sys_debug_setcontext));
+ allowSyscall(ctx, SCMP_SYS(sysfs));
+ allowSyscall(ctx, SCMP_SYS(sysinfo));
+ allowSyscall(ctx, SCMP_SYS(syslog));
+ allowSyscall(ctx, SCMP_SYS(sysmips));
+ allowSyscall(ctx, SCMP_SYS(tee));
+ allowSyscall(ctx, SCMP_SYS(tgkill));
+ allowSyscall(ctx, SCMP_SYS(time));
+ allowSyscall(ctx, SCMP_SYS(timer_create));
+ allowSyscall(ctx, SCMP_SYS(timer_delete));
+ allowSyscall(ctx, SCMP_SYS(timerfd));
+ allowSyscall(ctx, SCMP_SYS(timerfd_create));
+ allowSyscall(ctx, SCMP_SYS(timerfd_gettime));
+ allowSyscall(ctx, SCMP_SYS(timerfd_gettime64));
+ allowSyscall(ctx, SCMP_SYS(timerfd_settime));
+ allowSyscall(ctx, SCMP_SYS(timerfd_settime64));
+ allowSyscall(ctx, SCMP_SYS(timer_getoverrun));
+ allowSyscall(ctx, SCMP_SYS(timer_gettime));
+ allowSyscall(ctx, SCMP_SYS(timer_gettime64));
+ allowSyscall(ctx, SCMP_SYS(timer_settime));
+ allowSyscall(ctx, SCMP_SYS(timer_settime64));
+ allowSyscall(ctx, SCMP_SYS(times));
+ allowSyscall(ctx, SCMP_SYS(tkill));
+ allowSyscall(ctx, SCMP_SYS(truncate));
+ allowSyscall(ctx, SCMP_SYS(truncate64));
+ allowSyscall(ctx, SCMP_SYS(tuxcall));
+ allowSyscall(ctx, SCMP_SYS(ugetrlimit));
+ allowSyscall(ctx, SCMP_SYS(ulimit));
+ allowSyscall(ctx, SCMP_SYS(umask));
+ allowSyscall(ctx, SCMP_SYS(umount));
+ allowSyscall(ctx, SCMP_SYS(umount2));
+ allowSyscall(ctx, SCMP_SYS(uname));
+ allowSyscall(ctx, SCMP_SYS(unlink));
+ allowSyscall(ctx, SCMP_SYS(unlinkat));
+ allowSyscall(ctx, SCMP_SYS(unshare));
+ allowSyscall(ctx, SCMP_SYS(uselib));
+ allowSyscall(ctx, SCMP_SYS(userfaultfd));
+ allowSyscall(ctx, SCMP_SYS(usr26));
+ allowSyscall(ctx, SCMP_SYS(usr32));
+ allowSyscall(ctx, SCMP_SYS(ustat));
+ allowSyscall(ctx, SCMP_SYS(utime));
+ allowSyscall(ctx, SCMP_SYS(utimensat));
+ allowSyscall(ctx, SCMP_SYS(utimensat_time64));
+ allowSyscall(ctx, SCMP_SYS(utimes));
+ allowSyscall(ctx, SCMP_SYS(vfork));
+ allowSyscall(ctx, SCMP_SYS(vhangup));
+ allowSyscall(ctx, SCMP_SYS(vm86));
+ allowSyscall(ctx, SCMP_SYS(vm86old));
+ allowSyscall(ctx, SCMP_SYS(vmsplice));
+ allowSyscall(ctx, SCMP_SYS(vserver));
+ allowSyscall(ctx, SCMP_SYS(wait4));
+ allowSyscall(ctx, SCMP_SYS(waitid));
+ allowSyscall(ctx, SCMP_SYS(waitpid));
+ allowSyscall(ctx, SCMP_SYS(write));
+ allowSyscall(ctx, SCMP_SYS(writev));
+ // END extract-syscalls
+
+ // chmod family: prevent adding setuid/setgid bits to existing files.
+ // The Nix store does not support setuid/setgid, and even their temporary creation can weaken the security of the sandbox.
+ ALLOW_CHMOD_IF_SAFE(ctx, SCMP_SYS(chmod), 1);
+ ALLOW_CHMOD_IF_SAFE(ctx, SCMP_SYS(fchmod), 1);
+ ALLOW_CHMOD_IF_SAFE(ctx, SCMP_SYS(fchmodat), 2);
+ ALLOW_CHMOD_IF_SAFE(ctx, SCMP_SYS(fchmodat2), 2);
+
+ // setxattr family: prevent creation of extended attributes or ACLs.
+ // Not all filesystems support them, and they're incompatible with the NAR format.
if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ENOTSUP), SCMP_SYS(setxattr), 0) != 0 ||
seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ENOTSUP), SCMP_SYS(lsetxattr), 0) != 0 ||
seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ENOTSUP), SCMP_SYS(fsetxattr), 0) != 0)
@@ -1460,11 +1961,7 @@ void LocalDerivationGoal::runChild()
commonChildInit();
- try {
- setupSeccomp();
- } catch (...) {
- if (buildUser) throw;
- }
+ setupSeccomp();
bool setUser = true;
@@ -1882,7 +2379,7 @@ void LocalDerivationGoal::runChild()
sandboxArgs.push_back("_ALLOW_LOCAL_NETWORKING");
sandboxArgs.push_back("1");
}
- if (sandbox_init_with_parameters(sandboxProfile.c_str(), 0, stringsToCharPtrs(sandboxArgs).data(), NULL)) {
+ if (sandbox_init_with_parameters(sandboxProfile.c_str(), 0, stringsToCharPtrs(sandboxArgs).data(), nullptr)) {
writeFull(STDERR_FILENO, "failed to configure sandbox\n");
_exit(1);
}
diff --git a/src/libstore/build/substitution-goal.cc b/src/libstore/build/substitution-goal.cc
index cc4cb3c8c..027a7e161 100644
--- a/src/libstore/build/substitution-goal.cc
+++ b/src/libstore/build/substitution-goal.cc
@@ -86,7 +86,6 @@ void PathSubstitutionGoal::tryNext()
if (substituterFailed) {
worker.failedSubstitutions++;
- worker.updateProgress();
}
return;
@@ -150,8 +149,6 @@ void PathSubstitutionGoal::tryNext()
? std::make_unique<MaintainCount<uint64_t>>(worker.expectedDownloadSize, narInfo->fileSize)
: nullptr;
- worker.updateProgress();
-
/* Bail out early if this substituter lacks a valid
signature. LocalStore::addToStore() also checks for this, but
only after we've downloaded the path. */
@@ -210,13 +207,10 @@ void PathSubstitutionGoal::tryToRun()
}
maintainRunningSubstitutions = std::make_unique<MaintainCount<uint64_t>>(worker.runningSubstitutions);
- worker.updateProgress();
outPipe.create();
- promise = std::promise<void>();
-
- thr = std::thread([this]() {
+ thr = std::async(std::launch::async, [this]() {
auto & fetchPath = subPath ? *subPath : storePath;
try {
ReceiveInterrupts receiveInterrupts;
@@ -230,16 +224,12 @@ void PathSubstitutionGoal::tryToRun()
copyStorePath(
*sub, worker.store, fetchPath, repair, sub->isTrusted ? NoCheckSigs : CheckSigs
);
-
- promise.set_value();
} catch (const EndOfFile &) {
- promise.set_exception(std::make_exception_ptr(EndOfFile(
+ throw EndOfFile(
"NAR for '%s' fetched from '%s' is incomplete",
sub->printStorePath(fetchPath),
sub->getUri()
- )));
- } catch (...) {
- promise.set_exception(std::current_exception());
+ );
}
});
@@ -253,11 +243,10 @@ void PathSubstitutionGoal::finished()
{
trace("substitute finished");
- thr.join();
worker.childTerminated(this);
try {
- promise.get_future().get();
+ thr.get();
} catch (std::exception & e) {
printError(e.what());
@@ -296,8 +285,6 @@ void PathSubstitutionGoal::finished()
worker.doneNarSize += maintainExpectedNar->delta;
maintainExpectedNar.reset();
- worker.updateProgress();
-
done(ecSuccess, BuildResult::Substituted);
}
@@ -307,18 +294,12 @@ void PathSubstitutionGoal::handleChildOutput(int fd, std::string_view data)
}
-void PathSubstitutionGoal::handleEOF(int fd)
-{
- if (fd == outPipe.readSide.get()) worker.wakeUp(shared_from_this());
-}
-
-
void PathSubstitutionGoal::cleanup()
{
try {
- if (thr.joinable()) {
+ if (thr.valid()) {
// FIXME: signal worker thread to quit.
- thr.join();
+ thr.get();
worker.childTerminated(this);
}
diff --git a/src/libstore/build/substitution-goal.hh b/src/libstore/build/substitution-goal.hh
index 1d389d328..d85b3beb3 100644
--- a/src/libstore/build/substitution-goal.hh
+++ b/src/libstore/build/substitution-goal.hh
@@ -50,9 +50,7 @@ struct PathSubstitutionGoal : public Goal
/**
* The substituter thread.
*/
- std::thread thr;
-
- std::promise<void> promise;
+ std::future<void> thr;
/**
* Whether to try to repair a valid path.
@@ -112,7 +110,6 @@ public:
* Callback used by the worker to write to the log.
*/
void handleChildOutput(int fd, std::string_view data) override;
- void handleEOF(int fd) override;
/* Called by destructor, can't be overridden */
void cleanup() override final;
diff --git a/src/libstore/build/worker.cc b/src/libstore/build/worker.cc
index 39bcd5d92..5b2e36acb 100644
--- a/src/libstore/build/worker.cc
+++ b/src/libstore/build/worker.cc
@@ -157,21 +157,13 @@ void Worker::removeGoal(GoalPtr goal)
if (goal->exitCode == Goal::ecFailed && !settings.keepGoing)
topGoals.clear();
}
-
- /* Wake up goals waiting for any goal to finish. */
- for (auto & i : waitingForAnyGoal) {
- GoalPtr goal = i.lock();
- if (goal) wakeUp(goal);
- }
-
- waitingForAnyGoal.clear();
}
void Worker::wakeUp(GoalPtr goal)
{
goal->trace("woken up");
- addToWeakGoals(awake, goal);
+ awake.insert(goal);
}
@@ -213,7 +205,7 @@ void Worker::childStarted(GoalPtr goal, const std::set<int> & fds,
}
-void Worker::childTerminated(Goal * goal, bool wakeSleepers)
+void Worker::childTerminated(Goal * goal)
{
auto i = std::find_if(children.begin(), children.end(),
[&](const Child & child) { return child.goal2 == goal; });
@@ -236,16 +228,13 @@ void Worker::childTerminated(Goal * goal, bool wakeSleepers)
children.erase(i);
- if (wakeSleepers) {
-
- /* Wake up goals waiting for a build slot. */
- for (auto & j : wantingToBuild) {
- GoalPtr goal = j.lock();
- if (goal) wakeUp(goal);
- }
-
- wantingToBuild.clear();
+ /* Wake up goals waiting for a build slot. */
+ for (auto & j : wantingToBuild) {
+ GoalPtr goal = j.lock();
+ if (goal) wakeUp(goal);
}
+
+ wantingToBuild.clear();
}
@@ -257,21 +246,14 @@ void Worker::waitForBuildSlot(GoalPtr goal)
(isSubstitutionGoal && getNrSubstitutions() < settings.maxSubstitutionJobs))
wakeUp(goal); /* we can do it right away */
else
- addToWeakGoals(wantingToBuild, goal);
-}
-
-
-void Worker::waitForAnyGoal(GoalPtr goal)
-{
- debug("wait for any goal");
- addToWeakGoals(waitingForAnyGoal, goal);
+ wantingToBuild.insert(goal);
}
void Worker::waitForAWhile(GoalPtr goal)
{
debug("wait for a while");
- addToWeakGoals(waitingForAWhile, goal);
+ waitingForAWhile.insert(goal);
}
@@ -318,6 +300,19 @@ void Worker::run(const Goals & _topGoals)
for (auto & goal : awake2) {
checkInterrupt();
goal->work();
+
+ actDerivations.progress(
+ doneBuilds, expectedBuilds + doneBuilds, runningBuilds, failedBuilds
+ );
+ actSubstitutions.progress(
+ doneSubstitutions,
+ expectedSubstitutions + doneSubstitutions,
+ runningSubstitutions,
+ failedSubstitutions
+ );
+ act.setExpected(actFileTransfer, expectedDownloadSize + doneDownloadSize);
+ act.setExpected(actCopyPath, expectedNarSize + doneNarSize);
+
if (topGoals.empty()) break; // stuff may have been cancelled
}
}
@@ -429,7 +424,7 @@ void Worker::waitForInput()
GoalPtr goal = j->goal.lock();
assert(goal);
- if (goal->exitCode == Goal::ecBusy &&
+ if (!goal->exitCode.has_value() &&
0 != settings.maxSilentTime &&
j->respectTimeouts &&
after - j->lastOutput >= std::chrono::seconds(settings.maxSilentTime))
@@ -440,7 +435,7 @@ void Worker::waitForInput()
continue;
}
- else if (goal->exitCode == Goal::ecBusy &&
+ else if (!goal->exitCode.has_value() &&
0 != settings.buildTimeout &&
j->respectTimeouts &&
after - j->timeStarted >= std::chrono::seconds(settings.buildTimeout))
@@ -464,6 +459,7 @@ void Worker::waitForInput()
if (rd == 0 || (rd == -1 && errno == EIO)) {
debug("%1%: got EOF", goal->getName());
goal->handleEOF(k);
+ wakeUp(goal);
j->fds.erase(k);
} else if (rd == -1) {
if (errno != EINTR)
diff --git a/src/libstore/build/worker.hh b/src/libstore/build/worker.hh
index e1d8e5031..3984c9c1c 100644
--- a/src/libstore/build/worker.hh
+++ b/src/libstore/build/worker.hh
@@ -91,11 +91,6 @@ private:
std::map<DrvOutput, std::weak_ptr<DrvOutputSubstitutionGoal>> drvOutputSubstitutionGoals;
/**
- * Goals waiting for busy paths to be unlocked.
- */
- WeakGoals waitingForAnyGoal;
-
- /**
* Goals sleeping for a few seconds (polling a lock).
*/
WeakGoals waitingForAWhile;
@@ -151,7 +146,6 @@ public:
uint64_t doneSubstitutions = 0;
uint64_t failedSubstitutions = 0;
uint64_t runningSubstitutions = 0;
- uint64_t runningCASubstitutions = 0;
uint64_t expectedDownloadSize = 0;
uint64_t doneDownloadSize = 0;
uint64_t expectedNarSize = 0;
@@ -228,12 +222,9 @@ public:
bool inBuildSlot, bool respectTimeouts);
/**
- * Unregisters a running child process. `wakeSleepers` should be
- * false if there is no sense in waking up goals that are sleeping
- * because they can't run yet (e.g., there is no free build slot,
- * or the hook would still say `postpone`).
+ * Unregisters a running child process.
*/
- void childTerminated(Goal * goal, bool wakeSleepers = true);
+ void childTerminated(Goal * goal);
/**
* Put `goal` to sleep until a build slot becomes available (which
@@ -242,12 +233,6 @@ public:
void waitForBuildSlot(GoalPtr goal);
/**
- * Wait for any goal to finish. Pretty indiscriminate way to
- * wait for some resource that some other goal is holding.
- */
- void waitForAnyGoal(GoalPtr goal);
-
- /**
* Wait for a few seconds and then retry this goal. Used when
* waiting for a lock held by another process. This kind of
* polling is inefficient, but POSIX doesn't really provide a way
@@ -295,14 +280,6 @@ public:
bool pathContentsGood(const StorePath & path);
void markContentsGood(const StorePath & path);
-
- void updateProgress()
- {
- actDerivations.progress(doneBuilds, expectedBuilds + doneBuilds, runningBuilds, failedBuilds);
- actSubstitutions.progress(doneSubstitutions, expectedSubstitutions + doneSubstitutions, runningSubstitutions, failedSubstitutions);
- act.setExpected(actFileTransfer, expectedDownloadSize + doneDownloadSize);
- act.setExpected(actCopyPath, expectedNarSize + doneNarSize);
- }
};
}
diff --git a/src/libstore/daemon.cc b/src/libstore/daemon.cc
index 4162015d6..b09645f46 100644
--- a/src/libstore/daemon.cc
+++ b/src/libstore/daemon.cc
@@ -158,7 +158,7 @@ struct TunnelSink : Sink
{
Sink & to;
TunnelSink(Sink & to) : to(to) { }
- void operator () (std::string_view data)
+ void operator () (std::string_view data) override
{
to << STDERR_WRITE << data;
}
diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc
index 02d26e034..6cfa3ffac 100644
--- a/src/libstore/globals.cc
+++ b/src/libstore/globals.cc
@@ -177,14 +177,14 @@ static bool hasVirt() {
size_t size;
size = sizeof(hasVMM);
- if (sysctlbyname("kern.hv_vmm_present", &hasVMM, &size, NULL, 0) == 0) {
+ if (sysctlbyname("kern.hv_vmm_present", &hasVMM, &size, nullptr, 0) == 0) {
if (hasVMM)
return false;
}
// whether the kernel and hardware supports virt
size = sizeof(hvSupport);
- if (sysctlbyname("kern.hv_support", &hvSupport, &size, NULL, 0) == 0) {
+ if (sysctlbyname("kern.hv_support", &hvSupport, &size, nullptr, 0) == 0) {
return hvSupport == 1;
} else {
return false;
diff --git a/src/libstore/linux/fchmodat2-compat.hh b/src/libstore/linux/fchmodat2-compat.hh
deleted file mode 100644
index d5ef81e22..000000000
--- a/src/libstore/linux/fchmodat2-compat.hh
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Determine the syscall number for `fchmodat2`.
- *
- * On most platforms this is 452. Exceptions can be found on
- * a glibc git checkout via `rg --pcre2 'define __NR_fchmodat2 (?!452)'`.
- *
- * The problem is that glibc 2.39 and libseccomp 2.5.5 are needed to
- * get the syscall number. However, a Lix built against nixpkgs 23.11
- * (glibc 2.38) should still have the issue fixed without depending
- * on the build environment.
- *
- * To achieve that, the macros below try to determine the platform and
- * set the syscall number which is platform-specific, but
- * in most cases 452.
- *
- * TODO: remove this when 23.11 is EOL and the entire (supported) ecosystem
- * is on glibc 2.39.
- */
-
-#pragma once
-///@file
-
-#if defined(__alpha__)
-# define NIX_SYSCALL_FCHMODAT2 562
-#elif defined(__x86_64__) && SIZE_MAX == 0xFFFFFFFF // x32
-# define NIX_SYSCALL_FCHMODAT2 1073742276
-#elif defined(__mips__) && defined(__mips64) && defined(_ABIN64) // mips64/n64
-# define NIX_SYSCALL_FCHMODAT2 5452
-#elif defined(__mips__) && defined(__mips64) && defined(_ABIN32) // mips64/n32
-# define NIX_SYSCALL_FCHMODAT2 6452
-#elif defined(__mips__) && defined(_ABIO32) // mips32
-# define NIX_SYSCALL_FCHMODAT2 4452
-#else
-# define NIX_SYSCALL_FCHMODAT2 452
-#endif
diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
index 10d43ee3e..f09d1bdab 100644
--- a/src/libstore/local-store.cc
+++ b/src/libstore/local-store.cc
@@ -557,7 +557,7 @@ void LocalStore::openDB(State & state, bool create)
if (sqlite3_exec(db, "pragma main.journal_size_limit = 1099511627776;", 0, 0, 0) != SQLITE_OK)
SQLiteError::throw_(db, "setting journal_size_limit");
int enable = 1;
- if (sqlite3_file_control(db, NULL, SQLITE_FCNTL_PERSIST_WAL, &enable) != SQLITE_OK)
+ if (sqlite3_file_control(db, nullptr, SQLITE_FCNTL_PERSIST_WAL, &enable) != SQLITE_OK)
SQLiteError::throw_(db, "setting persistent WAL mode");
}
diff --git a/src/libstore/meson.build b/src/libstore/meson.build
index fa363bd19..5416bd2b5 100644
--- a/src/libstore/meson.build
+++ b/src/libstore/meson.build
@@ -167,6 +167,9 @@ if host_machine.system() == 'linux'
elif host_machine.system() == 'darwin'
libstore_sources += files('platform/darwin.cc')
libstore_headers += files('platform/darwin.hh')
+elif host_machine.system() == 'freebsd'
+ libstore_sources += files('platform/freebsd.cc')
+ libstore_headers += files('platform/freebsd.hh')
else
libstore_sources += files('platform/fallback.cc')
libstore_headers += files('platform/fallback.hh')
@@ -202,23 +205,29 @@ foreach name, value : cpp_str_defines
]
endforeach
+dependencies = [
+ libarchive,
+ liblixutil, # Internal.
+ seccomp,
+ sqlite,
+ sodium,
+ curl,
+ openssl,
+ aws_sdk,
+ aws_s3,
+ aws_sdk_transfer,
+ nlohmann_json,
+]
+
+if host_machine.system() == 'freebsd'
+ dependencies += [ libprocstat ]
+endif
+
libstore = library(
'lixstore',
libstore_generated_headers,
libstore_sources,
- dependencies : [
- libarchive,
- liblixutil, # Internal.
- seccomp,
- sqlite,
- sodium,
- curl,
- openssl,
- aws_sdk,
- aws_s3,
- aws_sdk_transfer,
- nlohmann_json,
- ],
+ dependencies : dependencies,
cpp_args : cpp_args,
cpp_pch : cpp_pch,
install : true,
diff --git a/src/libstore/platform.cc b/src/libstore/platform.cc
index d10d33f0e..72757e39b 100644
--- a/src/libstore/platform.cc
+++ b/src/libstore/platform.cc
@@ -5,6 +5,8 @@
#include "platform/linux.hh"
#elif __APPLE__
#include "platform/darwin.hh"
+#elif __FreeBSD__
+#include "platform/freebsd.hh"
#else
#include "platform/fallback.hh"
#endif
@@ -16,6 +18,8 @@ std::shared_ptr<LocalStore> LocalStore::makeLocalStore(const Params & params)
return std::shared_ptr<LocalStore>(new LinuxLocalStore(params));
#elif __APPLE__
return std::shared_ptr<LocalStore>(new DarwinLocalStore(params));
+#elif __FreeBSD__
+ return std::shared_ptr<LocalStore>(new FreeBSDLocalStore(params));
#else
return std::shared_ptr<LocalStore>(new FallbackLocalStore(params));
#endif
@@ -32,6 +36,8 @@ std::shared_ptr<LocalDerivationGoal> LocalDerivationGoal::makeLocalDerivationGoa
return std::make_shared<LinuxLocalDerivationGoal>(drvPath, wantedOutputs, worker, buildMode);
#elif __APPLE__
return std::make_shared<DarwinLocalDerivationGoal>(drvPath, wantedOutputs, worker, buildMode);
+#elif __FreeBSD__
+ return std::make_shared<FreeBSDLocalDerivationGoal>(drvPath, wantedOutputs, worker, buildMode);
#else
return std::make_shared<FallbackLocalDerivationGoal>(drvPath, wantedOutputs, worker, buildMode);
#endif
@@ -53,6 +59,10 @@ std::shared_ptr<LocalDerivationGoal> LocalDerivationGoal::makeLocalDerivationGoa
return std::make_shared<DarwinLocalDerivationGoal>(
drvPath, drv, wantedOutputs, worker, buildMode
);
+#elif __FreeBSD__
+ return std::make_shared<FreeBSDLocalDerivationGoal>(
+ drvPath, drv, wantedOutputs, worker, buildMode
+ );
#else
return std::make_shared<FallbackLocalDerivationGoal>(
drvPath, drv, wantedOutputs, worker, buildMode
diff --git a/src/libstore/platform/darwin.cc b/src/libstore/platform/darwin.cc
index 83b4b4183..1b591fde3 100644
--- a/src/libstore/platform/darwin.cc
+++ b/src/libstore/platform/darwin.cc
@@ -235,15 +235,15 @@ void DarwinLocalDerivationGoal::execBuilder(std::string builder, Strings args, S
if (drv->platform == "aarch64-darwin") {
// Unset kern.curproc_arch_affinity so we can escape Rosetta
int affinity = 0;
- sysctlbyname("kern.curproc_arch_affinity", NULL, NULL, &affinity, sizeof(affinity));
+ sysctlbyname("kern.curproc_arch_affinity", nullptr, nullptr, &affinity, sizeof(affinity));
cpu_type_t cpu = CPU_TYPE_ARM64;
- posix_spawnattr_setbinpref_np(&attrp, 1, &cpu, NULL);
+ posix_spawnattr_setbinpref_np(&attrp, 1, &cpu, nullptr);
} else if (drv->platform == "x86_64-darwin") {
cpu_type_t cpu = CPU_TYPE_X86_64;
- posix_spawnattr_setbinpref_np(&attrp, 1, &cpu, NULL);
+ posix_spawnattr_setbinpref_np(&attrp, 1, &cpu, nullptr);
}
- posix_spawn(NULL, builder.c_str(), NULL, &attrp, stringsToCharPtrs(args).data(), stringsToCharPtrs(envStrs).data());
+ posix_spawn(nullptr, builder.c_str(), nullptr, &attrp, stringsToCharPtrs(args).data(), stringsToCharPtrs(envStrs).data());
}
}
diff --git a/src/libstore/platform/freebsd.cc b/src/libstore/platform/freebsd.cc
new file mode 100644
index 000000000..bdba1abf5
--- /dev/null
+++ b/src/libstore/platform/freebsd.cc
@@ -0,0 +1,142 @@
+#include "platform/freebsd.hh"
+#include "regex.hh"
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/user.h>
+#include <libprocstat.h>
+
+namespace nix {
+
+static void readSysctlRoots(const char * name, UncheckedRoots & unchecked)
+{
+ size_t len = 0;
+ std::string value;
+ if (int err = sysctlbyname(name, nullptr, &len, nullptr, 0) < 0) {
+ if (err == ENOENT || err == EACCES) {
+ return;
+ } else {
+ throw SysError(err, "sysctlbyname %1%", name);
+ }
+ }
+
+ value.resize(len, ' ');
+ if (int err = sysctlbyname(name, value.data(), &len, nullptr, 0) < 0) {
+ if (err == ENOENT || err == EACCES) {
+ return;
+ } else {
+ throw SysError(err, "sysctlbyname %1%", name);
+ }
+ }
+
+ for (auto & path : tokenizeString<Strings>(value, ";")) {
+ unchecked[path].emplace(fmt("{{sysctl:%1%}}", name));
+ }
+}
+
+struct ProcstatDeleter
+{
+ void operator()(struct procstat * ps)
+ {
+ procstat_close(ps);
+ }
+};
+
+template<auto del>
+struct ProcstatReferredDeleter
+{
+ struct procstat * ps;
+
+ ProcstatReferredDeleter(struct procstat * ps) : ps(ps) {}
+
+ template<typename T>
+ void operator()(T * p)
+ {
+ del(ps, p);
+ }
+};
+
+void FreeBSDLocalStore::findPlatformRoots(UncheckedRoots & unchecked)
+{
+ readSysctlRoots("kern.module_path", unchecked);
+
+ auto storePathRegex = regex::storePathRegex(storeDir);
+
+ auto ps = std::unique_ptr<struct procstat, ProcstatDeleter>(procstat_open_sysctl());
+ if (!ps) {
+ throw SysError("procstat_open_sysctl");
+ }
+
+ auto procs = std::unique_ptr<struct kinfo_proc[], ProcstatReferredDeleter<procstat_freeprocs>>(
+ nullptr, ps.get()
+ );
+ auto files = std::unique_ptr<struct filestat_list, ProcstatReferredDeleter<procstat_freefiles>>(
+ nullptr, ps.get()
+ );
+
+ unsigned int numprocs = 0;
+ procs.reset(procstat_getprocs(ps.get(), KERN_PROC_PROC, 0, &numprocs));
+ if (!procs || numprocs == 0) {
+ throw SysError("procstat_getprocs");
+ };
+
+ for (unsigned int procidx = 0; procidx < numprocs; procidx++) {
+ // Includes file descriptors, executable, cwd,
+ // and mmapped files (including dynamic libraries)
+ files.reset(procstat_getfiles(ps.get(), &procs[procidx], 1));
+ // We only have permission if we're root so just skip it if we fail
+ if (!files) {
+ continue;
+ }
+
+ for (struct filestat * file = files->stqh_first; file; file = file->next.stqe_next) {
+ if (!file->fs_path) {
+ continue;
+ }
+
+ std::string role;
+ if (file->fs_uflags & PS_FST_UFLAG_CTTY) {
+ role = "ctty";
+ } else if (file->fs_uflags & PS_FST_UFLAG_CDIR) {
+ role = "cwd";
+ } else if (file->fs_uflags & PS_FST_UFLAG_JAIL) {
+ role = "jail";
+ } else if (file->fs_uflags & PS_FST_UFLAG_RDIR) {
+ role = "root";
+ } else if (file->fs_uflags & PS_FST_UFLAG_TEXT) {
+ role = "text";
+ } else if (file->fs_uflags & PS_FST_UFLAG_TRACE) {
+ role = "trace";
+ } else if (file->fs_uflags & PS_FST_UFLAG_MMAP) {
+ role = "mmap";
+ } else {
+ role = fmt("fd/%1%", file->fs_fd);
+ }
+
+ unchecked[file->fs_path].emplace(fmt("{procstat:%1%/%2%}", procs[procidx].ki_pid, role)
+ );
+ }
+
+ auto env_name = fmt("{procstat:%1%/env}", procs[procidx].ki_pid);
+ // No need to free, the buffer is reused on next call and deallocated in procstat_close
+ char ** env = procstat_getenvv(ps.get(), &procs[procidx], 0);
+ if (env == nullptr) {
+ continue;
+ }
+
+ for (size_t i = 0; env[i]; i++) {
+ auto envString = std::string(env[i]);
+
+ auto envEnd = std::sregex_iterator{};
+ for (auto match =
+ std::sregex_iterator{envString.begin(), envString.end(), storePathRegex};
+ match != envEnd;
+ match++)
+ {
+ unchecked[match->str()].emplace(env_name);
+ }
+ }
+ }
+}
+}
diff --git a/src/libstore/platform/freebsd.hh b/src/libstore/platform/freebsd.hh
new file mode 100644
index 000000000..99aff3c10
--- /dev/null
+++ b/src/libstore/platform/freebsd.hh
@@ -0,0 +1,47 @@
+#pragma once
+///@file
+
+#include "build/local-derivation-goal.hh"
+#include "gc-store.hh"
+#include "local-store.hh"
+
+namespace nix {
+
+/**
+ * FreeBSD-specific implementation of LocalStore
+ */
+class FreeBSDLocalStore : public LocalStore
+{
+public:
+ FreeBSDLocalStore(const Params & params)
+ : StoreConfig(params)
+ , LocalFSStoreConfig(params)
+ , LocalStoreConfig(params)
+ , Store(params)
+ , LocalFSStore(params)
+ , LocalStore(params)
+ {
+ }
+ FreeBSDLocalStore(const std::string scheme, std::string path, const Params & params)
+ : FreeBSDLocalStore(params)
+ {
+ throw UnimplementedError("FreeBSDLocalStore");
+ }
+
+private:
+
+ void findPlatformRoots(UncheckedRoots & unchecked) override;
+};
+
+/**
+ * FreeBSD-specific implementation of LocalDerivationGoal
+ */
+class FreeBSDLocalDerivationGoal : public LocalDerivationGoal
+{
+public:
+ using LocalDerivationGoal::LocalDerivationGoal;
+
+private:
+};
+
+}
diff --git a/src/libutil/concepts.hh b/src/libutil/concepts.hh
new file mode 100644
index 000000000..48bd1dbe1
--- /dev/null
+++ b/src/libutil/concepts.hh
@@ -0,0 +1,22 @@
+#pragma once
+/// @file Defines C++ 20 concepts that std doesn't have.
+
+#include <type_traits>
+
+namespace nix
+{
+
+/// Like std::invocable<>, but also constrains the return type as well.
+///
+/// Somehow, there is no std concept to do this, even though there is a type trait
+/// for it.
+///
+/// @tparam CallableT The type you want to constrain to be callable, and to return
+/// @p ReturnT when called with @p Args as arguments.
+///
+/// @tparam ReturnT The type the callable should return when called.
+/// @tparam Args The arguments the callable should accept to return @p ReturnT.
+template<typename CallableT, typename ReturnT, typename ...Args>
+concept InvocableR = std::is_invocable_r_v<ReturnT, CallableT, Args...>;
+
+}
diff --git a/src/libutil/current-process.cc b/src/libutil/current-process.cc
index 3c037c33f..33cda211b 100644
--- a/src/libutil/current-process.cc
+++ b/src/libutil/current-process.cc
@@ -15,6 +15,11 @@
# include <sys/resource.h>
#endif
+#if __FreeBSD__
+# include <sys/param.h>
+# include <sys/sysctl.h>
+#endif
+
#include <sys/mount.h>
#include <cgroup.hh>
@@ -102,6 +107,24 @@ std::optional<Path> getSelfExe()
return buf;
else
return std::nullopt;
+ #elif __FreeBSD__
+ int sysctlName[] = {
+ CTL_KERN,
+ KERN_PROC,
+ KERN_PROC_PATHNAME,
+ -1,
+ };
+ size_t pathLen = 0;
+ if (sysctl(sysctlName, sizeof(sysctlName) / sizeof(sysctlName[0]), nullptr, &pathLen, nullptr, 0) < 0) {
+ return std::nullopt;
+ }
+
+ std::vector<char> path(pathLen);
+ if (sysctl(sysctlName, sizeof(sysctlName) / sizeof(sysctlName[0]), path.data(), &pathLen, nullptr, 0) < 0) {
+ return std::nullopt;
+ }
+
+ return Path(path.begin(), path.end());
#else
return std::nullopt;
#endif
diff --git a/src/libutil/file-system.cc b/src/libutil/file-system.cc
index f0199d36f..e2319ec59 100644
--- a/src/libutil/file-system.cc
+++ b/src/libutil/file-system.cc
@@ -21,22 +21,14 @@ Path absPath(Path path, std::optional<PathView> dir, bool resolveSymlinks)
{
if (path.empty() || path[0] != '/') {
if (!dir) {
-#ifdef __GNU__
- /* GNU (aka. GNU/Hurd) doesn't have any limitation on path
- lengths and doesn't define `PATH_MAX'. */
- char *buf = getcwd(NULL, 0);
- if (buf == NULL)
-#else
char buf[PATH_MAX];
- if (!getcwd(buf, sizeof(buf)))
-#endif
+ if (!getcwd(buf, sizeof(buf))) {
throw SysError("cannot get cwd");
+ }
path = concatStrings(buf, "/", path);
-#ifdef __GNU__
- free(buf);
-#endif
- } else
+ } else {
path = concatStrings(*dir, "/", path);
+ }
}
return canonPath(path, resolveSymlinks);
}
diff --git a/src/libutil/logging.cc b/src/libutil/logging.cc
index d674c9651..53460f729 100644
--- a/src/libutil/logging.cc
+++ b/src/libutil/logging.cc
@@ -7,6 +7,7 @@
#include <algorithm>
#include <atomic>
+#include <mutex>
#include <sstream>
#include <nlohmann/json.hpp>
diff --git a/src/libutil/meson.build b/src/libutil/meson.build
index c860e7e00..01fe65207 100644
--- a/src/libutil/meson.build
+++ b/src/libutil/meson.build
@@ -58,6 +58,7 @@ libutil_headers = files(
'comparator.hh',
'compression.hh',
'compute-levels.hh',
+ 'concepts.hh',
'config-impl.hh',
'config.hh',
'current-process.hh',
diff --git a/src/libutil/processes.cc b/src/libutil/processes.cc
index 4fe3dcfb4..05b1321e9 100644
--- a/src/libutil/processes.cc
+++ b/src/libutil/processes.cc
@@ -251,7 +251,7 @@ std::pair<int, std::string> runProgram(RunOptions && options)
try {
auto proc = runProgram2(options);
Finally const _wait([&] { proc.wait(); });
- stdout = proc.stdout()->drain();
+ stdout = proc.getStdout()->drain();
} catch (ExecError & e) {
status = e.status;
}
diff --git a/src/libutil/processes.hh b/src/libutil/processes.hh
index f61b9696d..dc09a9ba4 100644
--- a/src/libutil/processes.hh
+++ b/src/libutil/processes.hh
@@ -106,7 +106,7 @@ public:
void wait();
- Source * stdout() const { return stdoutSource.get(); }
+ Source * getStdout() const { return stdoutSource.get(); };
};
std::pair<int, std::string> runProgram(RunOptions && options);
diff --git a/src/libutil/serialise.hh b/src/libutil/serialise.hh
index 9b1892bbb..6c637bd35 100644
--- a/src/libutil/serialise.hh
+++ b/src/libutil/serialise.hh
@@ -211,7 +211,7 @@ struct TeeSink : Sink
{
Sink & sink1, & sink2;
TeeSink(Sink & sink1, Sink & sink2) : sink1(sink1), sink2(sink2) { }
- virtual void operator () (std::string_view data)
+ virtual void operator () (std::string_view data) override
{
sink1(data);
sink2(data);
@@ -228,7 +228,7 @@ struct TeeSource : Source
Sink & sink;
TeeSource(Source & orig, Sink & sink)
: orig(orig), sink(sink) { }
- size_t read(char * data, size_t len)
+ size_t read(char * data, size_t len) override
{
size_t n = orig.read(data, len);
sink({data, n});
@@ -245,7 +245,7 @@ struct SizedSource : Source
size_t remain;
SizedSource(Source & orig, size_t size)
: orig(orig), remain(size) { }
- size_t read(char * data, size_t len)
+ size_t read(char * data, size_t len) override
{
if (this->remain <= 0) {
throw EndOfFile("sized: unexpected end-of-file");
@@ -338,7 +338,7 @@ struct GeneratorSource : Source
{
GeneratorSource(Generator<Bytes> && g) : g(std::move(g)) {}
- virtual size_t read(char * data, size_t len)
+ virtual size_t read(char * data, size_t len) override
{
// we explicitly do not poll the generator multiple times to fill the
// buffer, only to produce some output at all. this is allowed by the
diff --git a/src/libutil/tarfile.cc b/src/libutil/tarfile.cc
index 760a5a65a..c7f9499fd 100644
--- a/src/libutil/tarfile.cc
+++ b/src/libutil/tarfile.cc
@@ -54,7 +54,7 @@ TarArchive::TarArchive(Source & source, bool raw) : buffer(65536)
archive_read_support_format_raw(archive);
archive_read_support_format_empty(archive);
}
- archive_read_set_option(archive, NULL, "mac-ext", NULL);
+ archive_read_set_option(archive, nullptr, "mac-ext", nullptr);
check(archive_read_open(archive, (void *)this, callback_open, callback_read, callback_close), "Failed to open archive (%s)");
}
@@ -65,7 +65,7 @@ TarArchive::TarArchive(const Path & path)
archive_read_support_filter_all(archive);
archive_read_support_format_all(archive);
- archive_read_set_option(archive, NULL, "mac-ext", NULL);
+ archive_read_set_option(archive, nullptr, "mac-ext", nullptr);
check(archive_read_open_filename(archive, path.c_str(), 16384), "failed to open archive: %s");
}
diff --git a/src/meson.build b/src/meson.build
index 3fc5595b8..e918ae392 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -12,10 +12,19 @@ subdir('libmain')
# libcmd depends on everything
subdir('libcmd')
-
# The rest of the subdirectories aren't separate components,
# just source files in another directory, so we process them here.
+# Static library that just sets default ASan options. It needs to be included
+# in every executable.
+asanoptions = static_library(
+ 'libasanoptions',
+ files('asan-options/asan-options.cc'),
+)
+libasanoptions = declare_dependency(
+ link_whole: asanoptions
+)
+
build_remote_sources = files(
'build-remote/build-remote.cc',
)
diff --git a/src/nix/diff-closures.cc b/src/nix/diff-closures.cc
index 5eda309d5..dac4625e6 100644
--- a/src/nix/diff-closures.cc
+++ b/src/nix/diff-closures.cc
@@ -24,24 +24,17 @@ GroupedPaths getClosureInfo(ref<Store> store, const StorePath & toplevel)
GroupedPaths groupedPaths;
- for (auto & path : closure) {
+ for (auto const & path : closure) {
/* Strip the output name. Unfortunately this is ambiguous (we
can't distinguish between output names like "bin" and
version suffixes like "unstable"). */
static std::regex regex("(.*)-([a-z]+|lib32|lib64)");
- std::smatch match;
+ std::cmatch match;
std::string name{path.name()};
- // Used to keep name alive through being potentially overwritten below
- // (to not invalidate the references from the regex result)
- //
- // n.b. cannot be just path.name().{begin,end}() since that returns const
- // char *, which does not, for some reason, convert as required on
- // libstdc++. Seems like a libstdc++ bug or standard bug to me... we
- // can afford the allocation in any case.
- const std::string origName{path.name()};
+ std::string_view const origName = path.name();
std::string outputName;
- if (std::regex_match(origName, match, regex)) {
+ if (std::regex_match(origName.begin(), origName.end(), match, regex)) {
name = match[1];
outputName = match[2];
}
diff --git a/src/nix/meson.build b/src/nix/meson.build
index 22f148fcb..97387e402 100644
--- a/src/nix/meson.build
+++ b/src/nix/meson.build
@@ -80,6 +80,7 @@ nix = executable(
profiles_md_gen,
nix2_commands_sources,
dependencies : [
+ libasanoptions,
liblixcmd,
liblixutil_mstatic,
liblixstore_mstatic,
diff --git a/src/resolve-system-dependencies/resolve-system-dependencies.cc b/src/resolve-system-dependencies/resolve-system-dependencies.cc
index 2c4b06791..319f7108e 100644
--- a/src/resolve-system-dependencies/resolve-system-dependencies.cc
+++ b/src/resolve-system-dependencies/resolve-system-dependencies.cc
@@ -48,7 +48,7 @@ std::set<std::string> runResolver(const Path & filename)
return {};
}
- char* obj = (char*) mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd.get(), 0);
+ char* obj = (char*) mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd.get(), 0);
if (!obj)
throw SysError("mmapping '%s'", filename);
diff --git a/tests/functional/fetchers.sh b/tests/functional/fetchers.sh
new file mode 100644
index 000000000..0f888dc33
--- /dev/null
+++ b/tests/functional/fetchers.sh
@@ -0,0 +1,91 @@
+source common.sh
+
+requireGit
+
+clearStore
+
+testFetchTreeError() {
+ rawFetchTreeArg="${1?fetchTree arg missing}"
+ messageSubstring="${2?messageSubstring missing}"
+
+ output="$(nix eval --impure --raw --expr "(builtins.fetchTree $rawFetchTreeArg).outPath" 2>&1)" && status=0 || status=$?
+ grepQuiet "$messageSubstring" <<<"$output"
+ test "$status" -ne 0
+}
+
+# github/gitlab/sourcehut fetcher input validation
+for provider in github gitlab sourcehut; do
+ # ref/rev validation
+ testFetchTreeError \
+ "{ type = \"$provider\"; owner = \"foo\"; repo = \"bar\"; ref = \",\"; }" \
+ "URL '$provider:foo/bar' contains an invalid branch/tag name"
+
+ testFetchTreeError \
+ "\"$provider://host/foo/bar/,\"" \
+ "URL '$provider:foo/bar', ',' is not a commit hash or a branch/tag name"
+
+ testFetchTreeError \
+ "\"$provider://host/foo/bar/f16d8f43dd0998cdb315a2cccf2e4d10027e7ca4?rev=abc\"" \
+ "URL '$provider://host/foo/bar/f16d8f43dd0998cdb315a2cccf2e4d10027e7ca4?rev=abc' already contains a ref or rev"
+
+ testFetchTreeError \
+ "\"$provider://host/foo/bar/ref?ref=ref2\"" \
+ "URL '$provider://host/foo/bar/ref?ref=ref2' already contains a ref or rev"
+
+ # host validation
+ testFetchTreeError \
+ "{ type = \"$provider\"; owner = \"foo\"; repo = \"bar\"; host = \"git_hub.com\"; }" \
+ "URL '$provider:foo/bar' contains an invalid instance host"
+
+ testFetchTreeError \
+ "\"$provider://host/foo/bar/ref?host=git_hub.com\"" \
+ "URL '$provider:foo/bar' contains an invalid instance host"
+
+ # invalid attributes
+ testFetchTreeError \
+ "{ type = \"$provider\"; owner = \"foo\"; repo = \"bar\"; wrong = true; }" \
+ "unsupported input attribute 'wrong'"
+
+ testFetchTreeError \
+ "\"$provider://host/foo/bar/ref?wrong=1\"" \
+ "unsupported input attribute 'wrong'"
+done
+
+# unsupported attributes w/ tarball fetcher
+testFetchTreeError \
+ "\"https://host/foo?wrong=1\"" \
+ "unsupported tarball input attribute 'wrong'. If you wanted to fetch a tarball with a query parameter, please use '{ type = \"tarball\"; url = \"...\"; }"
+
+# test for unsupported attributes / validation in git fetcher
+testFetchTreeError \
+ "\"git+https://github.com/owner/repo?invalid=1\"" \
+ "unsupported Git input attribute 'invalid'"
+
+testFetchTreeError \
+ "\"git+https://github.com/owner/repo?url=foo\"" \
+ "URL 'git+https://github.com/owner/repo?url=foo' must not override url via query param!"
+
+testFetchTreeError \
+ "\"git+https://github.com/owner/repo?ref=foo.lock\"" \
+ "invalid Git branch/tag name 'foo.lock'"
+
+testFetchTreeError \
+ "{ type = \"git\"; url =\"https://github.com/owner/repo\"; ref = \"foo.lock\"; }" \
+ "invalid Git branch/tag name 'foo.lock'"
+
+# same for mercurial
+testFetchTreeError \
+ "\"hg+https://forge.tld/owner/repo?invalid=1\"" \
+ "unsupported Mercurial input attribute 'invalid'"
+
+testFetchTreeError \
+ "{ type = \"hg\"; url = \"https://forge.tld/owner/repo\"; invalid = 1; }" \
+ "unsupported Mercurial input attribute 'invalid'"
+
+testFetchTreeError \
+ "\"hg+https://forge.tld/owner/repo?ref=,\"" \
+ "invalid Mercurial branch/tag name ','"
+
+testFetchTreeError \
+ "{ type = \"hg\"; url = \"https://forge.tld/owner/repo\"; ref = \",\"; }" \
+ "invalid Mercurial branch/tag name ','"
diff --git a/tests/functional/flakes/subdir-flake.sh b/tests/functional/flakes/subdir-flake.sh
new file mode 100644
index 000000000..399518502
--- /dev/null
+++ b/tests/functional/flakes/subdir-flake.sh
@@ -0,0 +1,20 @@
+source common.sh
+requireGit
+clearStore
+
+container="$TEST_HOME"/flake-container
+flake_dir="$container"/flake-dir
+
+createGitRepo "$container"
+mkdir -p "$flake_dir"
+writeSimpleFlake "$flake_dir"
+git -C "$container" add flake-dir
+
+pushd "$flake_dir" &>/dev/null
+ info="$(nix flake info --json)"
+ [[ "$(jq -r '.resolvedUrl' <<<"$info")" == git+file://*/flake-container?dir=flake-dir ]]
+ [[ "$(jq -r '.url' <<<"$info")" == git+file://*/flake-container?dir=flake-dir ]]
+
+ # Make sure we can actually access & build stuff in this flake.
+ nix build "path:$flake_dir#foo" -L
+popd &>/dev/null
diff --git a/tests/functional/meson.build b/tests/functional/meson.build
index cbf6a1563..7a9c7182f 100644
--- a/tests/functional/meson.build
+++ b/tests/functional/meson.build
@@ -71,6 +71,7 @@ functional_tests_scripts = [
'flakes/build-paths.sh',
'flakes/flake-registry.sh',
'flakes/flake-in-submodule.sh',
+ 'flakes/subdir-flake.sh',
'gc.sh',
'nix-collect-garbage-d.sh',
'nix-collect-garbage-dry-run.sh',
@@ -94,6 +95,7 @@ functional_tests_scripts = [
'fetchGitRefs.sh',
'gc-runtime.sh',
'tarball.sh',
+ 'fetchers.sh',
'fetchGit.sh',
'fetchurl.sh',
'fetchPath.sh',
diff --git a/tests/functional/repl.sh b/tests/functional/repl.sh
index cd56b4d92..22c69e20b 100644
--- a/tests/functional/repl.sh
+++ b/tests/functional/repl.sh
@@ -244,3 +244,30 @@ testReplResponseNoRegex '
y = { a = 1; };
}
'
+
+# Test that editing a store path does not reload...
+echo '{ identity = a: a; }' > repl-test.nix
+repl_test_store="$(nix-store --add repl-test.nix)"
+EDITOR=true testReplResponseNoRegex "
+a = ''test string that we'll grep later''
+:l $repl_test_store
+:e identity
+a
+" "test string that we'll grep later"
+
+# ...even through symlinks
+ln -s "$repl_test_store" repl-test-link.nix
+EDITOR=true testReplResponseNoRegex "
+a = ''test string that we'll grep later''
+:l repl-test-link.nix
+:e identity
+a
+" "test string that we'll grep later"
+
+# Test that editing a local file does reload
+EDITOR=true testReplResponseNoRegex "
+a = ''test string that we'll grep later''
+:l repl-test.nix
+:e identity
+a
+" "undefined variable"
diff --git a/tests/functional/repl_characterization/meson.build b/tests/functional/repl_characterization/meson.build
index 56410cfd2..79de9a5f5 100644
--- a/tests/functional/repl_characterization/meson.build
+++ b/tests/functional/repl_characterization/meson.build
@@ -7,6 +7,7 @@ repl_characterization_tester = executable(
'test-repl-characterization',
repl_characterization_tester_sources,
dependencies : [
+ libasanoptions,
liblixutil,
liblixutil_test_support,
sodium,
diff --git a/tests/functional/test-libstoreconsumer/meson.build b/tests/functional/test-libstoreconsumer/meson.build
index ad96aac12..63d0c97ac 100644
--- a/tests/functional/test-libstoreconsumer/meson.build
+++ b/tests/functional/test-libstoreconsumer/meson.build
@@ -2,6 +2,7 @@ libstoreconsumer_tester = executable(
'test-libstoreconsumer',
'main.cc',
dependencies : [
+ libasanoptions,
liblixutil,
liblixstore,
sodium,
diff --git a/tests/nixos/default.nix b/tests/nixos/default.nix
index 301eede46..20e66f6c1 100644
--- a/tests/nixos/default.nix
+++ b/tests/nixos/default.nix
@@ -155,4 +155,6 @@ in
broken-userns = runNixOSTestFor "x86_64-linux" ./broken-userns.nix;
coredumps = runNixOSTestFor "x86_64-linux" ./coredumps;
+
+ io_uring = runNixOSTestFor "x86_64-linux" ./io_uring;
}
diff --git a/tests/nixos/io_uring/default.nix b/tests/nixos/io_uring/default.nix
new file mode 100644
index 000000000..9cd445d6a
--- /dev/null
+++ b/tests/nixos/io_uring/default.nix
@@ -0,0 +1,7 @@
+let
+ inherit (import ../util.nix) mkNixBuildTest;
+in
+mkNixBuildTest {
+ name = "io_uring";
+ expressionFile = ./package.nix;
+}
diff --git a/tests/nixos/io_uring/package.nix b/tests/nixos/io_uring/package.nix
new file mode 100644
index 000000000..8f980183a
--- /dev/null
+++ b/tests/nixos/io_uring/package.nix
@@ -0,0 +1,19 @@
+{ runCommandCC }:
+runCommandCC "io_uring-is-blocked" { } ''
+ cat > test.c <<EOF
+ #include <errno.h>
+ #include <sys/syscall.h>
+ #include <unistd.h>
+
+ int main() {
+ int res = syscall(SYS_io_uring_setup, 0, NULL);
+ return res == -1 && errno == ENOSYS ? 0 : 1;
+ }
+ EOF
+ "$CC" -o test test.c
+ if ! ./test; then
+ echo "Oh no! io_uring is available!"
+ exit 1
+ fi
+ touch "$out"
+''
diff --git a/tests/nixos/setuid/fchmodat2-suid.c b/tests/nixos/setuid/fchmodat2-suid.c
index 931489ad7..7280331d5 100644
--- a/tests/nixos/setuid/fchmodat2-suid.c
+++ b/tests/nixos/setuid/fchmodat2-suid.c
@@ -12,10 +12,7 @@ int main(void) {
fprintf(fd, "henlo :3");
fclose(fd);
- // FIXME use something nicer here that's less
- // platform-dependent as soon as we go to 24.05
- // and the glibc is new enough to support fchmodat2
- long rs = syscall(452, NULL, name, S_ISUID, 0);
+ long rs = syscall(SYS_fchmodat2, NULL, name, S_ISUID, 0);
assert(rs == -1);
assert(errno == EPERM);
}
diff --git a/tests/nixos/tarball-flakes.nix b/tests/nixos/tarball-flakes.nix
index ca7627bd1..5deba4a12 100644
--- a/tests/nixos/tarball-flakes.nix
+++ b/tests/nixos/tarball-flakes.nix
@@ -69,7 +69,7 @@ in
# Check that we got redirected to the immutable URL.
locked_url = info["locked"]["url"]
- assert locked_url == "http://localhost/stable/${nixpkgs.rev}.tar.gz", f"{locked_url=} != http://localhost/stable/${nixpkgs.rev}.tar.gz"
+ assert locked_url == "http://localhost/stable/${nixpkgs.rev}.tar.gz?rev=${nixpkgs.rev}&revCount=1234", f"{locked_url=} != http://localhost/stable/${nixpkgs.rev}.tar.gz"
# Check that we got the rev and revCount attributes.
revision = info["revision"]
diff --git a/tests/unit/meson.build b/tests/unit/meson.build
index c449b2276..55c7566bd 100644
--- a/tests/unit/meson.build
+++ b/tests/unit/meson.build
@@ -11,6 +11,10 @@
# functions, the result would be way less readable than just a bit of copypasta.
# It's only ~200 lines; better to just refactor the tests themselves which we'll want to do anyway.
+default_test_env = {
+ 'ASAN_OPTIONS': 'detect_leaks=0:halt_on_error=1:abort_on_error=1:print_summary=1:dump_instruction_bytes=1'
+}
+
libutil_test_support_sources = files(
'libutil-support/tests/cli-literate-parser.cc',
'libutil-support/tests/hash.cc',
@@ -63,6 +67,7 @@ libutil_tester = executable(
'liblixutil-tests',
libutil_tests_sources,
dependencies : [
+ libasanoptions,
rapidcheck,
gtest,
boehm,
@@ -78,7 +83,7 @@ test(
'libutil-unit-tests',
libutil_tester,
args : tests_args,
- env : {
+ env : default_test_env + {
'_NIX_TEST_UNIT_DATA': meson.project_source_root() / 'tests/unit/libutil/data',
},
suite : 'check',
@@ -132,6 +137,7 @@ libstore_tester = executable(
'liblixstore-tests',
libstore_tests_sources,
dependencies : [
+ libasanoptions,
liblixstore_test_support,
liblixutil_test_support,
liblixstore_mstatic,
@@ -147,7 +153,7 @@ test(
'libstore-unit-tests',
libstore_tester,
args : tests_args,
- env : {
+ env : default_test_env + {
'_NIX_TEST_UNIT_DATA': meson.project_source_root() / 'tests/unit/libstore/data',
},
suite : 'check',
@@ -196,6 +202,7 @@ libexpr_tester = executable(
'liblixexpr-tests',
libexpr_tests_sources,
dependencies : [
+ libasanoptions,
liblixexpr_test_support,
liblixstore_test_support,
liblixstore_mstatic,
@@ -214,7 +221,7 @@ test(
'libexpr-unit-tests',
libexpr_tester,
args : tests_args,
- env : {
+ env : default_test_env + {
'_NIX_TEST_UNIT_DATA': meson.project_source_root() / 'tests/unit/libexpr/data',
},
suite : 'check',
@@ -226,6 +233,7 @@ libcmd_tester = executable(
'liblixcmd-tests',
files('libcmd/args.cc'),
dependencies : [
+ libasanoptions,
liblixcmd,
liblixutil,
liblixmain,
@@ -241,7 +249,7 @@ test(
'libcmd-unit-tests',
libcmd_tester,
args : tests_args,
- env : {
+ env : default_test_env + {
# No special meaning here, it's just a file laying around that is unlikely to go anywhere
# any time soon.
'_NIX_TEST_UNIT_DATA': meson.project_source_root() / 'src/nix-env/buildenv.nix',
@@ -272,6 +280,7 @@ test(
'libmain-unit-tests',
libmain_tester,
args : tests_args,
+ env : default_test_env,
suite : 'check',
protocol : 'gtest',
)
diff --git a/version.json b/version.json
index 48db2994f..809358e6d 100644
--- a/version.json
+++ b/version.json
@@ -1,4 +1,5 @@
{
"version": "2.91.0-dev",
+ "official_release": false,
"release_name": "TBA"
}