diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index addafb9f82d8..6169c0924881 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ permissions: read-all jobs: eval: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 with: @@ -20,8 +20,15 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-latest] - runs-on: ${{ matrix.os }} + include: + - scenario: on ubuntu + runs-on: ubuntu-24.04 + os: linux + - scenario: on macos + runs-on: macos-14 + os: darwin + name: tests ${{ matrix.scenario }} + runs-on: ${{ matrix.runs-on }} timeout-minutes: 60 steps: - uses: actions/checkout@v4 @@ -37,7 +44,7 @@ jobs: # Since ubuntu 22.30, unprivileged usernamespaces are no longer allowed to map to the root user: # https://ubuntu.com/blog/ubuntu-23-10-restricted-unprivileged-user-namespaces - run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 - if: matrix.os == 'ubuntu-latest' + if: matrix.os == 'linux' - run: scripts/build-checks - run: scripts/prepare-installer-for-github-actions - name: Upload installer tarball @@ -51,8 +58,15 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-latest] - runs-on: ${{ matrix.os }} + include: + - scenario: on ubuntu + runs-on: ubuntu-24.04 + os: linux + - scenario: on macos + runs-on: macos-14 + os: darwin + name: installer test ${{ matrix.scenario }} + runs-on: ${{ matrix.runs-on }} steps: - uses: actions/checkout@v4 - name: Download installer tarball @@ -68,9 +82,9 @@ jobs: install_url: 'http://localhost:8126/install' install_options: "--tarball-url-prefix http://localhost:8126/" - run: sudo apt install fish zsh - if: matrix.os == 'ubuntu-latest' + if: matrix.os == 'linux' - run: brew install fish - if: matrix.os == 'macos-latest' + if: matrix.os == 'darwin' - run: exec bash -c "nix-instantiate -E 'builtins.currentTime' --eval" - run: exec sh -c "nix-instantiate -E 'builtins.currentTime' --eval" - run: exec zsh -c "nix-instantiate -E 'builtins.currentTime' --eval" @@ -86,7 +100,7 @@ jobs: permissions: contents: none name: Check Docker secrets present for installer tests - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: docker: ${{ steps.secret.outputs.docker }} steps: @@ -106,7 +120,7 @@ jobs: needs.check_secrets.outputs.docker == 'true' && github.event_name == 'push' && github.ref_name == 'master' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Check for secrets id: secret @@ -158,7 +172,7 @@ jobs: docker push $IMAGE_ID:master vm_tests: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - uses: DeterminateSystems/nix-installer-action@main @@ -173,7 +187,7 @@ jobs: flake_regressions: needs: vm_tests - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout nix uses: actions/checkout@v4 diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 34aa4e6bdf04..23a5d9e51fce 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -15,7 +15,7 @@ permissions: jobs: labels: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: github.repository_owner == 'NixOS' steps: - uses: actions/labeler@v5 diff --git a/.mergify.yml b/.mergify.yml index 70fccae4911e..5d2bf8520dc6 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -2,10 +2,10 @@ queue_rules: - name: default # all required tests need to go here merge_conditions: - - check-success=tests (macos-latest) - - check-success=tests (ubuntu-latest) - - check-success=installer_test (macos-latest) - - check-success=installer_test (ubuntu-latest) + - check-success=tests on macos + - check-success=tests on ubuntu + - check-success=installer test on macos + - check-success=installer test on ubuntu - check-success=vm_tests batch_size: 5 diff --git a/doc/manual/source/command-ref/nix-collect-garbage.md b/doc/manual/source/command-ref/nix-collect-garbage.md index bd05f28164ee..763179b8ee18 100644 --- a/doc/manual/source/command-ref/nix-collect-garbage.md +++ b/doc/manual/source/command-ref/nix-collect-garbage.md @@ -62,6 +62,15 @@ These options are for deleting old [profiles] prior to deleting unreachable [sto This is the equivalent of invoking [`nix-env --delete-generations `](@docroot@/command-ref/nix-env/delete-generations.md#generations-time) on each found profile. See the documentation of that command for additional information about the *period* argument. + - [`--max-freed`](#opt-max-freed) *bytes* + + + + Keep deleting paths until at least *bytes* bytes have been deleted, + then stop. The argument *bytes* can be followed by the + multiplicative suffix `K`, `M`, `G` or `T`, denoting KiB, MiB, GiB + or TiB units. + {{#include ./opt-common.md}} {{#include ./env-common.md}} diff --git a/doc/manual/source/command-ref/nix-store/add-fixed.md b/doc/manual/source/command-ref/nix-store/add-fixed.md index bebf15026a69..2ea90a135925 100644 --- a/doc/manual/source/command-ref/nix-store/add-fixed.md +++ b/doc/manual/source/command-ref/nix-store/add-fixed.md @@ -21,6 +21,9 @@ This operation has the following options: Use recursive instead of flat hashing mode, used when adding directories to the store. + *paths* that refer to symlinks are not dereferenced, but added to the store + as symlinks with the same target. + {{#include ./opt-common.md}} {{#include ../opt-common.md}} diff --git a/doc/manual/source/command-ref/nix-store/add.md b/doc/manual/source/command-ref/nix-store/add.md index 87d504cd3338..ab474072311d 100644 --- a/doc/manual/source/command-ref/nix-store/add.md +++ b/doc/manual/source/command-ref/nix-store/add.md @@ -11,6 +11,9 @@ The operation `--add` adds the specified paths to the Nix store. It prints the resulting paths in the Nix store on standard output. +*paths* that refer to symlinks are not dereferenced, but added to the store +as symlinks with the same target. + {{#include ./opt-common.md}} {{#include ../opt-common.md}} diff --git a/doc/manual/source/development/documentation.md b/doc/manual/source/development/documentation.md index 2e188f23246d..30cc8adc44a5 100644 --- a/doc/manual/source/development/documentation.md +++ b/doc/manual/source/development/documentation.md @@ -19,10 +19,11 @@ nix-build -E '(import ./.).packages.${builtins.currentSystem}.nix.doc' or ```console -nix build .#nix^doc +nix build .#nix-manual ``` -and open `./result-doc/share/doc/nix/manual/index.html`. +and open `./result/share/doc/nix/manual/index.html`. + To build the manual incrementally, [enter the development shell](./building.md) and run: diff --git a/doc/manual/source/development/testing.md b/doc/manual/source/development/testing.md index 30aa7d0d51b9..d582ce4b4131 100644 --- a/doc/manual/source/development/testing.md +++ b/doc/manual/source/development/testing.md @@ -297,7 +297,7 @@ Creating a Cachix cache for your installer tests and adding its authorisation to - `armv7l-linux` - `x86_64-darwin` -- The `installer_test` job (which runs on `ubuntu-latest` and `macos-latest`) will try to install Nix with the cached installer and run a trivial Nix command. +- The `installer_test` job (which runs on `ubuntu-24.04` and `macos-14`) will try to install Nix with the cached installer and run a trivial Nix command. ### One-time setup diff --git a/nix-meson-build-support/common/meson.build b/nix-meson-build-support/common/meson.build index f0322183e8a8..67b6658f5941 100644 --- a/nix-meson-build-support/common/meson.build +++ b/nix-meson-build-support/common/meson.build @@ -16,7 +16,3 @@ add_project_arguments( '-Wno-deprecated-declarations', language : 'cpp', ) - -if get_option('buildtype') not in ['debug'] - add_project_arguments('-O3', language : 'cpp') -endif diff --git a/packaging/dependencies.nix b/packaging/dependencies.nix index 2c7cf701f25e..4bc7495e7560 100644 --- a/packaging/dependencies.nix +++ b/packaging/dependencies.nix @@ -66,6 +66,21 @@ let mesonLayer = finalAttrs: prevAttrs: { + # NOTE: + # As of https://github.com/NixOS/nixpkgs/blob/8baf8241cea0c7b30e0b8ae73474cb3de83c1a30/pkgs/by-name/me/meson/setup-hook.sh#L26, + # `mesonBuildType` defaults to `plain` if not specified. We want our Nix-built binaries to be optimized by default. + # More on build types here: https://mesonbuild.com/Builtin-options.html#details-for-buildtype. + mesonBuildType = "release"; + # NOTE: + # Users who are debugging Nix builds are expected to set the environment variable `mesonBuildType`, per the + # guidance in https://github.com/NixOS/nix/blob/8a3fc27f1b63a08ac983ee46435a56cf49ebaf4a/doc/manual/source/development/debugging.md?plain=1#L10. + # For this reason, we don't want to refer to `finalAttrs.mesonBuildType` here, but rather use the environment variable. + preConfigure = prevAttrs.preConfigure or "" + '' + case "$mesonBuildType" in + release|minsize) appendToVar mesonFlags "-Db_lto=true" ;; + *) appendToVar mesonFlags "-Db_lto=false" ;; + esac + ''; nativeBuildInputs = [ pkgs.buildPackages.meson pkgs.buildPackages.ninja diff --git a/src/libcmd/meson.build b/src/libcmd/meson.build index 914db0108842..4145f408a092 100644 --- a/src/libcmd/meson.build +++ b/src/libcmd/meson.build @@ -4,8 +4,6 @@ project('nix-cmd', 'cpp', 'cpp_std=c++2a', # TODO(Qyriad): increase the warning level 'warning_level=1', - 'debug=true', - 'optimization=2', 'errorlogs=true', # Please print logs for tests that fail ], meson_version : '>= 1.1', diff --git a/src/libexpr-c/meson.build b/src/libexpr-c/meson.build index 1556dae519cf..9487132cf369 100644 --- a/src/libexpr-c/meson.build +++ b/src/libexpr-c/meson.build @@ -4,8 +4,6 @@ project('nix-expr-c', 'cpp', 'cpp_std=c++2a', # TODO(Qyriad): increase the warning level 'warning_level=1', - 'debug=true', - 'optimization=2', 'errorlogs=true', # Please print logs for tests that fail ], meson_version : '>= 1.1', diff --git a/src/libexpr-test-support/meson.build b/src/libexpr-test-support/meson.build index 64d4fe21862b..56e814cd1321 100644 --- a/src/libexpr-test-support/meson.build +++ b/src/libexpr-test-support/meson.build @@ -4,8 +4,6 @@ project('nix-expr-test-support', 'cpp', 'cpp_std=c++2a', # TODO(Qyriad): increase the warning level 'warning_level=1', - 'debug=true', - 'optimization=2', 'errorlogs=true', # Please print logs for tests that fail ], meson_version : '>= 1.1', diff --git a/src/libexpr-tests/meson.build b/src/libexpr-tests/meson.build index f37e85e5722c..667a0d7b7a8b 100644 --- a/src/libexpr-tests/meson.build +++ b/src/libexpr-tests/meson.build @@ -4,8 +4,6 @@ project('nix-expr-tests', 'cpp', 'cpp_std=c++2a', # TODO(Qyriad): increase the warning level 'warning_level=1', - 'debug=true', - 'optimization=2', 'errorlogs=true', # Please print logs for tests that fail ], meson_version : '>= 1.1', diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index fe5f05ab8874..21dd5a2944d8 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -3185,12 +3185,16 @@ std::ostream & operator << (std::ostream & str, const ExternalValueBase & v) { return v.print(str); } -void forceNoNullByte(std::string_view s) +void forceNoNullByte(std::string_view s, std::function pos) { if (s.find('\0') != s.npos) { using namespace std::string_view_literals; auto str = replaceStrings(std::string(s), "\0"sv, "␀"sv); - throw Error("input string '%s' cannot be represented as Nix string because it contains null bytes", str); + Error error("input string '%s' cannot be represented as Nix string because it contains null bytes", str); + if (pos) { + error.atPos(pos()); + } + throw error; } } diff --git a/src/libexpr/lexer.l b/src/libexpr/lexer.l index a7e44cb72883..067f86e01618 100644 --- a/src/libexpr/lexer.l +++ b/src/libexpr/lexer.l @@ -41,16 +41,18 @@ namespace nix { // we make use of the fact that the parser receives a private copy of the input // string and can munge around in it. -static StringToken unescapeStr(SymbolTable & symbols, char * s, size_t length) +// getting the position is expensive and thus it is implemented lazily. +static StringToken unescapeStr(char * const s, size_t length, std::function && pos) { - char * result = s; + bool noNullByte = true; char * t = s; - char c; // the input string is terminated with *two* NULs, so we can safely take // *one* character after the one being checked against. - while ((c = *s++)) { + for (size_t i = 0; i < length; t++) { + char c = s[i++]; + noNullByte &= c != '\0'; if (c == '\\') { - c = *s++; + c = s[i++]; if (c == 'n') *t = '\n'; else if (c == 'r') *t = '\r'; else if (c == 't') *t = '\t'; @@ -59,12 +61,14 @@ static StringToken unescapeStr(SymbolTable & symbols, char * s, size_t length) else if (c == '\r') { /* Normalise CR and CR/LF into LF. */ *t = '\n'; - if (*s == '\n') s++; /* cr/lf */ + if (s[i] == '\n') i++; /* cr/lf */ } else *t = c; - t++; } - return {result, size_t(t - result)}; + if (!noNullByte) { + forceNoNullByte({s, size_t(t - s)}, std::move(pos)); + } + return {s, size_t(t - s)}; } static void requireExperimentalFeature(const ExperimentalFeature & feature, const Pos & pos) @@ -175,7 +179,7 @@ or { return OR_KW; } /* It is impossible to match strings ending with '$' with one regex because trailing contexts are only valid at the end of a rule. (A sane but undocumented limitation.) */ - yylval->str = unescapeStr(state->symbols, yytext, yyleng); + yylval->str = unescapeStr(yytext, yyleng, [&]() { return state->positions[CUR_POS]; }); return STR; } \$\{ { PUSH_STATE(DEFAULT); return DOLLAR_CURLY; } @@ -191,6 +195,7 @@ or { return OR_KW; } \'\'(\ *\n)? { PUSH_STATE(IND_STRING); return IND_STRING_OPEN; } ([^\$\']|\$[^\{\']|\'[^\'\$])+ { yylval->str = {yytext, (size_t) yyleng, true}; + forceNoNullByte(yylval->str, [&]() { return state->positions[CUR_POS]; }); return IND_STR; } \'\'\$ | @@ -203,7 +208,7 @@ or { return OR_KW; } return IND_STR; } \'\'\\{ANY} { - yylval->str = unescapeStr(state->symbols, yytext + 2, yyleng - 2); + yylval->str = unescapeStr(yytext + 2, yyleng - 2, [&]() { return state->positions[CUR_POS]; }); return IND_STR; } \$\{ { PUSH_STATE(DEFAULT); return DOLLAR_CURLY; } diff --git a/src/libexpr/meson.build b/src/libexpr/meson.build index b3c559ba71c1..b33aebc86a5a 100644 --- a/src/libexpr/meson.build +++ b/src/libexpr/meson.build @@ -4,8 +4,6 @@ project('nix-expr', 'cpp', 'cpp_std=c++2a', # TODO(Qyriad): increase the warning level 'warning_level=1', - 'debug=true', - 'optimization=2', 'errorlogs=true', # Please print logs for tests that fail ], meson_version : '>= 1.1', diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh index 88fcae986068..8925693e3d0d 100644 --- a/src/libexpr/value.hh +++ b/src/libexpr/value.hh @@ -510,6 +510,6 @@ typedef std::shared_ptr RootValue; RootValue allocRootValue(Value * v); -void forceNoNullByte(std::string_view s); +void forceNoNullByte(std::string_view s, std::function = nullptr); } diff --git a/src/libfetchers-tests/meson.build b/src/libfetchers-tests/meson.build index 3e82c61110d1..739435501266 100644 --- a/src/libfetchers-tests/meson.build +++ b/src/libfetchers-tests/meson.build @@ -4,8 +4,6 @@ project('nix-fetchers-tests', 'cpp', 'cpp_std=c++2a', # TODO(Qyriad): increase the warning level 'warning_level=1', - 'debug=true', - 'optimization=2', 'errorlogs=true', # Please print logs for tests that fail ], meson_version : '>= 1.1', diff --git a/src/libfetchers/fetchers.cc b/src/libfetchers/fetchers.cc index b105c252a300..87bf0dd716a5 100644 --- a/src/libfetchers/fetchers.cc +++ b/src/libfetchers/fetchers.cc @@ -66,7 +66,7 @@ Input Input::fromURL( } } - throw Error("input '%s' is unsupported", url.url); + throw Error("input '%s' is unsupported", url); } Input Input::fromAttrs(const Settings & settings, Attrs && attrs) diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index 41864c694e9e..c4dfc27a27f2 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -435,7 +435,7 @@ struct GitInputScheme : InputScheme // // See: https://discourse.nixos.org/t/57783 and #9708 // - repoInfo.url = repoInfo.isLocal ? std::filesystem::absolute(url.path).string() : url.base; + repoInfo.url = repoInfo.isLocal ? std::filesystem::absolute(url.path).string() : url.to_string(); // If this is a local directory and no ref or revision is // given, then allow the use of an unclean working tree. diff --git a/src/libfetchers/github.cc b/src/libfetchers/github.cc index 308cff33a461..185941988473 100644 --- a/src/libfetchers/github.cc +++ b/src/libfetchers/github.cc @@ -50,7 +50,7 @@ struct GitArchiveInputScheme : InputScheme 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]); + throw BadURL("in URL '%s', '%s' is not a commit hash or branch/tag name", url, path[2]); } else if (size > 3) { std::string rs; for (auto i = std::next(path.begin(), 2); i != path.end(); i++) { @@ -63,34 +63,34 @@ struct GitArchiveInputScheme : InputScheme if (std::regex_match(rs, refRegex)) { ref = rs; } else { - throw BadURL("in URL '%s', '%s' is not a branch/tag name", url.url, rs); + throw BadURL("in URL '%s', '%s' is not a branch/tag name", url, rs); } } else if (size < 2) - throw BadURL("URL '%s' is invalid", url.url); + throw BadURL("URL '%s' is invalid", url); for (auto &[name, value] : url.query) { if (name == "rev") { if (rev) - throw BadURL("URL '%s' contains multiple commit hashes", url.url); + throw BadURL("URL '%s' contains multiple commit hashes", url); rev = Hash::parseAny(value, HashAlgorithm::SHA1); } else if (name == "ref") { if (!std::regex_match(value, refRegex)) - throw BadURL("URL '%s' contains an invalid branch/tag name", url.url); + throw BadURL("URL '%s' contains an invalid branch/tag name", url); if (ref) - throw BadURL("URL '%s' contains multiple branch/tag names", url.url); + throw BadURL("URL '%s' contains multiple branch/tag names", url); ref = value; } else if (name == "host") { if (!std::regex_match(value, hostRegex)) - throw BadURL("URL '%s' contains an invalid instance host", url.url); + throw BadURL("URL '%s' contains an invalid instance host", url); host_url = 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()); + throw BadURL("URL '%s' contains both a commit hash and a branch/tag name %s %s", url, *ref, rev->gitRev()); Input input{settings}; input.attrs.insert_or_assign("type", std::string { schemeName() }); diff --git a/src/libfetchers/indirect.cc b/src/libfetchers/indirect.cc index 2e5cd82c78df..0e1b86711f03 100644 --- a/src/libfetchers/indirect.cc +++ b/src/libfetchers/indirect.cc @@ -26,16 +26,16 @@ struct IndirectInputScheme : InputScheme else if (std::regex_match(path[1], refRegex)) ref = path[1]; else - throw BadURL("in flake URL '%s', '%s' is not a commit hash or branch/tag name", url.url, path[1]); + throw BadURL("in flake URL '%s', '%s' is not a commit hash or branch/tag name", 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]); + throw BadURL("in flake URL '%s', '%s' is not a branch/tag name", 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]); + throw BadURL("in flake URL '%s', '%s' is not a commit hash", url, path[2]); rev = Hash::parseAny(path[2], HashAlgorithm::SHA1); } else - throw BadURL("GitHub URL '%s' is invalid", url.url); + throw BadURL("GitHub URL '%s' is invalid", url); std::string id = path[0]; if (!std::regex_match(id, flakeRegex)) diff --git a/src/libfetchers/mercurial.cc b/src/libfetchers/mercurial.cc index 2c987f79d50a..c2fd8139cb3c 100644 --- a/src/libfetchers/mercurial.cc +++ b/src/libfetchers/mercurial.cc @@ -161,7 +161,7 @@ struct MercurialInputScheme : InputScheme { auto url = parseURL(getStrAttr(input.attrs, "url")); bool isLocal = url.scheme == "file"; - return {isLocal, isLocal ? url.path : url.base}; + return {isLocal, isLocal ? url.path : url.to_string()}; } StorePath fetchToStore(ref store, Input & input) const diff --git a/src/libfetchers/meson.build b/src/libfetchers/meson.build index b4408e94318a..58afbb7d08b0 100644 --- a/src/libfetchers/meson.build +++ b/src/libfetchers/meson.build @@ -4,8 +4,6 @@ project('nix-fetchers', 'cpp', 'cpp_std=c++2a', # TODO(Qyriad): increase the warning level 'warning_level=1', - 'debug=true', - 'optimization=2', 'errorlogs=true', # Please print logs for tests that fail ], meson_version : '>= 1.1', diff --git a/src/libfetchers/path.cc b/src/libfetchers/path.cc index 246b68c3a9be..f628e042cbec 100644 --- a/src/libfetchers/path.cc +++ b/src/libfetchers/path.cc @@ -14,7 +14,7 @@ struct PathInputScheme : InputScheme if (url.scheme != "path") return {}; if (url.authority && *url.authority != "") - throw Error("path URL '%s' should not have an authority ('%s')", url.url, *url.authority); + throw Error("path URL '%s' should not have an authority ('%s')", url, *url.authority); Input input{settings}; input.attrs.insert_or_assign("type", "path"); @@ -27,10 +27,10 @@ struct PathInputScheme : InputScheme if (auto n = string2Int(value)) input.attrs.insert_or_assign(name, *n); else - throw Error("path URL '%s' has invalid parameter '%s'", url.to_string(), name); + throw Error("path URL '%s' has invalid parameter '%s'", url, name); } else - throw Error("path URL '%s' has unsupported parameter '%s'", url.to_string(), name); + throw Error("path URL '%s' has unsupported parameter '%s'", url, name); return input; } diff --git a/src/libflake-c/meson.build b/src/libflake-c/meson.build index b7669fe97789..85d20644d595 100644 --- a/src/libflake-c/meson.build +++ b/src/libflake-c/meson.build @@ -4,8 +4,6 @@ project('nix-flake-c', 'cpp', 'cpp_std=c++2a', # TODO(Qyriad): increase the warning level 'warning_level=1', - 'debug=true', - 'optimization=2', 'errorlogs=true', # Please print logs for tests that fail ], meson_version : '>= 1.1', diff --git a/src/libflake-tests/meson.build b/src/libflake-tests/meson.build index 5c3c58e53a82..1c8765f21d6e 100644 --- a/src/libflake-tests/meson.build +++ b/src/libflake-tests/meson.build @@ -4,8 +4,6 @@ project('nix-flake-tests', 'cpp', 'cpp_std=c++2a', # TODO(Qyriad): increase the warning level 'warning_level=1', - 'debug=true', - 'optimization=2', 'errorlogs=true', # Please print logs for tests that fail ], meson_version : '>= 1.1', diff --git a/src/libflake/flake/flakeref.cc b/src/libflake/flake/flakeref.cc index ab882fdaba6e..60efe1612d24 100644 --- a/src/libflake/flake/flakeref.cc +++ b/src/libflake/flake/flakeref.cc @@ -159,11 +159,7 @@ std::pair parsePathFlakeRefWithFragment( while (flakeRoot != "/") { if (pathExists(flakeRoot + "/.git")) { - auto base = std::string("git+file://") + flakeRoot; - auto parsedURL = ParsedURL{ - .url = base, // FIXME - .base = base, .scheme = "git+file", .authority = "", .path = flakeRoot, @@ -220,8 +216,6 @@ static std::optional> parseFlakeIdRef( if (std::regex_match(url, match, flakeRegex)) { auto parsedURL = ParsedURL{ - .url = url, - .base = "flake:" + match.str(1), .scheme = "flake", .authority = "", .path = match[1], diff --git a/src/libflake/meson.build b/src/libflake/meson.build index f9e2177294ea..b757d0d7633f 100644 --- a/src/libflake/meson.build +++ b/src/libflake/meson.build @@ -4,8 +4,6 @@ project('nix-flake', 'cpp', 'cpp_std=c++2a', # TODO(Qyriad): increase the warning level 'warning_level=1', - 'debug=true', - 'optimization=2', 'errorlogs=true', # Please print logs for tests that fail ], meson_version : '>= 1.1', diff --git a/src/libmain-c/meson.build b/src/libmain-c/meson.build index 5a5684b8d634..d875d2c3f550 100644 --- a/src/libmain-c/meson.build +++ b/src/libmain-c/meson.build @@ -4,8 +4,6 @@ project('nix-main-c', 'cpp', 'cpp_std=c++2a', # TODO(Qyriad): increase the warning level 'warning_level=1', - 'debug=true', - 'optimization=2', 'errorlogs=true', # Please print logs for tests that fail ], meson_version : '>= 1.1', diff --git a/src/libmain/meson.build b/src/libmain/meson.build index 87fc8b8d29fe..00f945f494bb 100644 --- a/src/libmain/meson.build +++ b/src/libmain/meson.build @@ -4,8 +4,6 @@ project('nix-main', 'cpp', 'cpp_std=c++2a', # TODO(Qyriad): increase the warning level 'warning_level=1', - 'debug=true', - 'optimization=2', 'errorlogs=true', # Please print logs for tests that fail ], meson_version : '>= 1.1', diff --git a/src/libstore-c/meson.build b/src/libstore-c/meson.build index 1ac331ad0fbb..17d18609f094 100644 --- a/src/libstore-c/meson.build +++ b/src/libstore-c/meson.build @@ -4,8 +4,6 @@ project('nix-store-c', 'cpp', 'cpp_std=c++2a', # TODO(Qyriad): increase the warning level 'warning_level=1', - 'debug=true', - 'optimization=2', 'errorlogs=true', # Please print logs for tests that fail ], meson_version : '>= 1.1', diff --git a/src/libstore-test-support/meson.build b/src/libstore-test-support/meson.build index 2a07e56ac7f0..59d649889e26 100644 --- a/src/libstore-test-support/meson.build +++ b/src/libstore-test-support/meson.build @@ -4,8 +4,6 @@ project('nix-store-test-support', 'cpp', 'cpp_std=c++2a', # TODO(Qyriad): increase the warning level 'warning_level=1', - 'debug=true', - 'optimization=2', 'errorlogs=true', # Please print logs for tests that fail ], meson_version : '>= 1.1', diff --git a/src/libstore-tests/meson.build b/src/libstore-tests/meson.build index b706fa12c2fd..3ba0795e9faa 100644 --- a/src/libstore-tests/meson.build +++ b/src/libstore-tests/meson.build @@ -4,8 +4,6 @@ project('nix-store-tests', 'cpp', 'cpp_std=c++2a', # TODO(Qyriad): increase the warning level 'warning_level=1', - 'debug=true', - 'optimization=2', 'errorlogs=true', # Please print logs for tests that fail ], meson_version : '>= 1.1', diff --git a/src/libstore/meson.build b/src/libstore/meson.build index 12a0e6376441..79d912497227 100644 --- a/src/libstore/meson.build +++ b/src/libstore/meson.build @@ -4,8 +4,6 @@ project('nix-store', 'cpp', 'cpp_std=c++2a', # TODO(Qyriad): increase the warning level 'warning_level=1', - 'debug=true', - 'optimization=2', 'errorlogs=true', # Please print logs for tests that fail 'localstatedir=/nix/var', ], diff --git a/src/libstore/windows/pathlocks.cc b/src/libstore/windows/pathlocks.cc index 197f5a1c46d4..29a98d8e2310 100644 --- a/src/libstore/windows/pathlocks.cc +++ b/src/libstore/windows/pathlocks.cc @@ -3,7 +3,7 @@ #include "signals.hh" #include "util.hh" -#ifdef WIN32 +#ifdef _WIN32 # include # include # include diff --git a/src/libutil-c/meson.build b/src/libutil-c/meson.build index 44cec1afc8e6..ac1297665801 100644 --- a/src/libutil-c/meson.build +++ b/src/libutil-c/meson.build @@ -4,8 +4,6 @@ project('nix-util-c', 'cpp', 'cpp_std=c++2a', # TODO(Qyriad): increase the warning level 'warning_level=1', - 'debug=true', - 'optimization=2', 'errorlogs=true', # Please print logs for tests that fail ], meson_version : '>= 1.1', diff --git a/src/libutil-test-support/meson.build b/src/libutil-test-support/meson.build index 03ae63f1a319..db944cf0619f 100644 --- a/src/libutil-test-support/meson.build +++ b/src/libutil-test-support/meson.build @@ -4,8 +4,6 @@ project('nix-util-test-support', 'cpp', 'cpp_std=c++2a', # TODO(Qyriad): increase the warning level 'warning_level=1', - 'debug=true', - 'optimization=2', 'errorlogs=true', # Please print logs for tests that fail ], meson_version : '>= 1.1', diff --git a/src/libutil-tests/file-system.cc b/src/libutil-tests/file-system.cc index 7ef804f3403c..2c10d486986a 100644 --- a/src/libutil-tests/file-system.cc +++ b/src/libutil-tests/file-system.cc @@ -261,4 +261,18 @@ TEST(pathExists, bogusPathDoesNotExist) { ASSERT_FALSE(pathExists("/schnitzel/darmstadt/pommes")); } + +/* ---------------------------------------------------------------------------- + * makeParentCanonical + * --------------------------------------------------------------------------*/ + +TEST(makeParentCanonical, noParent) +{ + ASSERT_EQ(makeParentCanonical("file"), absPath(std::filesystem::path("file"))); +} + +TEST(makeParentCanonical, root) +{ + ASSERT_EQ(makeParentCanonical("/"), "/"); +} } diff --git a/src/libutil-tests/meson.build b/src/libutil-tests/meson.build index 83ac79e92b8b..ad2c61711cd4 100644 --- a/src/libutil-tests/meson.build +++ b/src/libutil-tests/meson.build @@ -4,8 +4,6 @@ project('nix-util-tests', 'cpp', 'cpp_std=c++2a', # TODO(Qyriad): increase the warning level 'warning_level=1', - 'debug=true', - 'optimization=2', 'errorlogs=true', # Please print logs for tests that fail ], meson_version : '>= 1.1', diff --git a/src/libutil-tests/url.cc b/src/libutil-tests/url.cc index 7d08f467eac5..7e1d2aa15eec 100644 --- a/src/libutil-tests/url.cc +++ b/src/libutil-tests/url.cc @@ -20,24 +20,11 @@ namespace nix { } - std::ostream& operator<<(std::ostream& os, const ParsedURL& p) { - return os << "\n" - << "url: " << p.url << "\n" - << "base: " << p.base << "\n" - << "scheme: " << p.scheme << "\n" - << "authority: " << p.authority.value() << "\n" - << "path: " << p.path << "\n" - << "query: " << print_map(p.query) << "\n" - << "fragment: " << p.fragment << "\n"; - } - TEST(parseURL, parsesSimpleHttpUrl) { auto s = "http://www.example.org/file.tar.gz"; auto parsed = parseURL(s); ParsedURL expected { - .url = "http://www.example.org/file.tar.gz", - .base = "http://www.example.org/file.tar.gz", .scheme = "http", .authority = "www.example.org", .path = "/file.tar.gz", @@ -53,8 +40,6 @@ namespace nix { auto parsed = parseURL(s); ParsedURL expected { - .url = "https://www.example.org/file.tar.gz", - .base = "https://www.example.org/file.tar.gz", .scheme = "https", .authority = "www.example.org", .path = "/file.tar.gz", @@ -70,8 +55,6 @@ namespace nix { auto parsed = parseURL(s); ParsedURL expected { - .url = "https://www.example.org/file.tar.gz", - .base = "https://www.example.org/file.tar.gz", .scheme = "https", .authority = "www.example.org", .path = "/file.tar.gz", @@ -87,8 +70,6 @@ namespace nix { auto parsed = parseURL(s); ParsedURL expected { - .url = "http://www.example.org/file.tar.gz", - .base = "http://www.example.org/file.tar.gz", .scheme = "http", .authority = "www.example.org", .path = "/file.tar.gz", @@ -104,8 +85,6 @@ namespace nix { auto parsed = parseURL(s); ParsedURL expected { - .url = "file+https://www.example.org/video.mp4", - .base = "https://www.example.org/video.mp4", .scheme = "file+https", .authority = "www.example.org", .path = "/video.mp4", @@ -126,8 +105,6 @@ namespace nix { auto parsed = parseURL(s); ParsedURL expected { - .url = "http://127.0.0.1:8080/file.tar.gz", - .base = "https://127.0.0.1:8080/file.tar.gz", .scheme = "http", .authority = "127.0.0.1:8080", .path = "/file.tar.gz", @@ -143,8 +120,6 @@ namespace nix { auto parsed = parseURL(s); ParsedURL expected { - .url = "http://[fe80::818c:da4d:8975:415c\%enp0s25]:8080", - .base = "http://[fe80::818c:da4d:8975:415c\%enp0s25]:8080", .scheme = "http", .authority = "[fe80::818c:da4d:8975:415c\%enp0s25]:8080", .path = "", @@ -161,8 +136,6 @@ namespace nix { auto parsed = parseURL(s); ParsedURL expected { - .url = "http://[2a02:8071:8192:c100:311d:192d:81ac:11ea]:8080", - .base = "http://[2a02:8071:8192:c100:311d:192d:81ac:11ea]:8080", .scheme = "http", .authority = "[2a02:8071:8192:c100:311d:192d:81ac:11ea]:8080", .path = "", @@ -185,8 +158,6 @@ namespace nix { auto parsed = parseURL(s); ParsedURL expected { - .url = "http://user:pass@www.example.org/file.tar.gz", - .base = "http://user:pass@www.example.org/file.tar.gz", .scheme = "http", .authority = "user:pass@www.example.org:8080", .path = "/file.tar.gz", @@ -203,8 +174,6 @@ namespace nix { auto parsed = parseURL(s); ParsedURL expected { - .url = "", - .base = "", .scheme = "file", .authority = "", .path = "/none/of//your/business", @@ -228,8 +197,6 @@ namespace nix { auto parsed = parseURL(s); ParsedURL expected { - .url = "ftp://ftp.nixos.org/downloads/nixos.iso", - .base = "ftp://ftp.nixos.org/downloads/nixos.iso", .scheme = "ftp", .authority = "ftp.nixos.org", .path = "/downloads/nixos.iso", diff --git a/src/libutil/file-system.cc b/src/libutil/file-system.cc index 793eb2d9fb8e..923220fd0b27 100644 --- a/src/libutil/file-system.cc +++ b/src/libutil/file-system.cc @@ -766,4 +766,19 @@ bool isExecutableFileAmbient(const fs::path & exe) { ) == 0; } +std::filesystem::path makeParentCanonical(const std::filesystem::path & rawPath) +{ + std::filesystem::path path(absPath(rawPath));; + try { + auto parent = path.parent_path(); + if (parent == path) { + // `path` is a root directory => trivially canonical + return parent; + } + return std::filesystem::canonical(parent) / path.filename(); + } catch (fs::filesystem_error & e) { + throw SysError("canonicalising parent path of '%1%'", path); + } } + +} // namespace nix diff --git a/src/libutil/file-system.hh b/src/libutil/file-system.hh index 3c49181a0ad1..7fdaba8111e7 100644 --- a/src/libutil/file-system.hh +++ b/src/libutil/file-system.hh @@ -143,6 +143,23 @@ inline bool symlink_exists(const std::filesystem::path & path) { } // namespace fs +/** + * Canonicalize a path except for the last component. + * + * This is useful for getting the canonical location of a symlink. + * + * Consider the case where `foo/l` is a symlink. `canonical("foo/l")` will + * resolve the symlink `l` to its target. + * `makeParentCanonical("foo/l")` will not resolve the symlink `l` to its target, + * but does ensure that the returned parent part of the path, `foo` is resolved + * to `canonical("foo")`, and can therefore be retrieved without traversing any + * symlinks. + * + * If a relative path is passed, it will be made absolute, so that the parent + * can always be canonicalized. + */ +std::filesystem::path makeParentCanonical(const std::filesystem::path & path); + /** * A version of pathExists that returns false on a permission error. * Useful for inferring default paths across directories that might not diff --git a/src/libutil/meson.build b/src/libutil/meson.build index 2c3e3a954b55..ac701d8fd3b9 100644 --- a/src/libutil/meson.build +++ b/src/libutil/meson.build @@ -4,8 +4,6 @@ project('nix-util', 'cpp', 'cpp_std=c++2a', # TODO(Qyriad): increase the warning level 'warning_level=1', - 'debug=true', - 'optimization=2', 'errorlogs=true', # Please print logs for tests that fail ], meson_version : '>= 1.1', diff --git a/src/libutil/posix-source-accessor.hh b/src/libutil/posix-source-accessor.hh index 40f60bb54b8f..5d491e633ce0 100644 --- a/src/libutil/posix-source-accessor.hh +++ b/src/libutil/posix-source-accessor.hh @@ -43,13 +43,25 @@ struct PosixSourceAccessor : virtual SourceAccessor std::optional getPhysicalPath(const CanonPath & path) override; /** - * Create a `PosixSourceAccessor` and `CanonPath` corresponding to + * Create a `PosixSourceAccessor` and `SourcePath` corresponding to * some native path. * * The `PosixSourceAccessor` is rooted as far up the tree as * possible, (e.g. on Windows it could scoped to a drive like * `C:\`). This allows more `..` parent accessing to work. * + * @note When `path` is trusted user input, canonicalize it using + * `std::filesystem::canonical`, `makeParentCanonical`, `std::filesystem::weakly_canonical`, etc, + * as appropriate for the use case. At least weak canonicalization is + * required for the `SourcePath` to do anything useful at the location it + * points to. + * + * @note A canonicalizing behavior is not built in `createAtRoot` so that + * callers do not accidentally introduce symlink-related security vulnerabilities. + * Furthermore, `createAtRoot` does not know whether the file pointed to by + * `path` should be resolved if it is itself a symlink. In other words, + * `createAtRoot` can not decide between aforementioned `canonical`, `makeParentCanonical`, etc. for its callers. + * * See * [`std::filesystem::path::root_path`](https://en.cppreference.com/w/cpp/filesystem/path/root_path) * and diff --git a/src/libutil/url.cc b/src/libutil/url.cc index 63b9734ee518..4d4d983b8dbc 100644 --- a/src/libutil/url.cc +++ b/src/libutil/url.cc @@ -40,8 +40,6 @@ ParsedURL parseURL(const std::string & url) path = "/"; return ParsedURL{ - .url = url, - .base = base, .scheme = scheme, .authority = authority, .path = percentDecode(path), @@ -136,6 +134,12 @@ std::string ParsedURL::to_string() const + (fragment.empty() ? "" : "#" + percentEncode(fragment)); } +std::ostream & operator << (std::ostream & os, const ParsedURL & url) +{ + os << url.to_string(); + return os; +} + bool ParsedURL::operator ==(const ParsedURL & other) const noexcept { return diff --git a/src/libutil/url.hh b/src/libutil/url.hh index 738ee9f82e64..2b12f5af2a0c 100644 --- a/src/libutil/url.hh +++ b/src/libutil/url.hh @@ -7,9 +7,6 @@ namespace nix { struct ParsedURL { - std::string url; - /// URL without query/fragment - std::string base; std::string scheme; std::optional authority; std::string path; @@ -26,6 +23,8 @@ struct ParsedURL ParsedURL canonicalise(); }; +std::ostream & operator << (std::ostream & os, const ParsedURL & url); + MakeError(BadURL, Error); std::string percentDecode(std::string_view in); diff --git a/src/libutil/windows/environment-variables.cc b/src/libutil/windows/environment-variables.cc index 308a432e4d84..d1093597cfb7 100644 --- a/src/libutil/windows/environment-variables.cc +++ b/src/libutil/windows/environment-variables.cc @@ -1,6 +1,6 @@ #include "environment-variables.hh" -#ifdef WIN32 +#ifdef _WIN32 # include "processenv.h" namespace nix { diff --git a/src/libutil/windows/file-descriptor.cc b/src/libutil/windows/file-descriptor.cc index 71f53ccb8869..e2a473a7cce7 100644 --- a/src/libutil/windows/file-descriptor.cc +++ b/src/libutil/windows/file-descriptor.cc @@ -5,7 +5,7 @@ #include "windows-error.hh" #include "file-path.hh" -#ifdef WIN32 +#ifdef _WIN32 #include #include #include diff --git a/src/libutil/windows/file-system.cc b/src/libutil/windows/file-system.cc index 53271cef3da9..7ed1c04a6233 100644 --- a/src/libutil/windows/file-system.cc +++ b/src/libutil/windows/file-system.cc @@ -1,6 +1,6 @@ #include "file-system.hh" -#ifdef WIN32 +#ifdef _WIN32 namespace nix { Descriptor openDirectory(const std::filesystem::path & path) diff --git a/src/libutil/windows/muxable-pipe.cc b/src/libutil/windows/muxable-pipe.cc index af7e987e9f29..ac28821202cd 100644 --- a/src/libutil/windows/muxable-pipe.cc +++ b/src/libutil/windows/muxable-pipe.cc @@ -1,4 +1,4 @@ -#ifdef WIN32 +#ifdef _WIN32 # include # include "windows-error.hh" diff --git a/src/libutil/windows/os-string.cc b/src/libutil/windows/os-string.cc index 26ad9cba0c8b..b09ef8b90d23 100644 --- a/src/libutil/windows/os-string.cc +++ b/src/libutil/windows/os-string.cc @@ -7,7 +7,7 @@ #include "file-path-impl.hh" #include "util.hh" -#ifdef WIN32 +#ifdef _WIN32 namespace nix { diff --git a/src/libutil/windows/processes.cc b/src/libutil/windows/processes.cc index e69f1ed45a52..fd4d7c43a918 100644 --- a/src/libutil/windows/processes.cc +++ b/src/libutil/windows/processes.cc @@ -23,7 +23,7 @@ #include #include -#ifdef WIN32 +#ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include diff --git a/src/libutil/windows/users.cc b/src/libutil/windows/users.cc index 2780e45f4a19..438c4221cf34 100644 --- a/src/libutil/windows/users.cc +++ b/src/libutil/windows/users.cc @@ -4,7 +4,7 @@ #include "file-system.hh" #include "windows-error.hh" -#ifdef WIN32 +#ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include diff --git a/src/libutil/windows/windows-async-pipe.cc b/src/libutil/windows/windows-async-pipe.cc index 13b563510290..4e139d5cfb92 100644 --- a/src/libutil/windows/windows-async-pipe.cc +++ b/src/libutil/windows/windows-async-pipe.cc @@ -1,7 +1,7 @@ #include "windows-async-pipe.hh" #include "windows-error.hh" -#ifdef WIN32 +#ifdef _WIN32 namespace nix::windows { diff --git a/src/libutil/windows/windows-async-pipe.hh b/src/libutil/windows/windows-async-pipe.hh index 277336ed76a8..53715e26010c 100644 --- a/src/libutil/windows/windows-async-pipe.hh +++ b/src/libutil/windows/windows-async-pipe.hh @@ -2,7 +2,7 @@ ///@file #include "file-descriptor.hh" -#ifdef WIN32 +#ifdef _WIN32 namespace nix::windows { diff --git a/src/libutil/windows/windows-error.cc b/src/libutil/windows/windows-error.cc index 4cf4274dae7d..b92f9155f972 100644 --- a/src/libutil/windows/windows-error.cc +++ b/src/libutil/windows/windows-error.cc @@ -1,6 +1,6 @@ #include "windows-error.hh" -#ifdef WIN32 +#ifdef _WIN32 #include #define WIN32_LEAN_AND_MEAN #include diff --git a/src/libutil/windows/windows-error.hh b/src/libutil/windows/windows-error.hh index 4e48ee8599cc..66c67b43a6c4 100644 --- a/src/libutil/windows/windows-error.hh +++ b/src/libutil/windows/windows-error.hh @@ -1,7 +1,7 @@ #pragma once ///@file -#ifdef WIN32 +#ifdef _WIN32 #include #include "error.hh" diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc index b731b25afedc..99bb2c72601a 100644 --- a/src/nix-store/nix-store.cc +++ b/src/nix-store/nix-store.cc @@ -183,9 +183,9 @@ static void opAdd(Strings opFlags, Strings opArgs) if (!opFlags.empty()) throw UsageError("unknown flag"); for (auto & i : opArgs) { - auto [accessor, canonPath] = PosixSourceAccessor::createAtRoot(i); + auto sourcePath = PosixSourceAccessor::createAtRoot(makeParentCanonical(i)); cout << fmt("%s\n", store->printStorePath(store->addToStore( - std::string(baseNameOf(i)), {accessor, canonPath}))); + std::string(baseNameOf(i)), sourcePath))); } } @@ -207,10 +207,10 @@ static void opAddFixed(Strings opFlags, Strings opArgs) opArgs.pop_front(); for (auto & i : opArgs) { - auto [accessor, canonPath] = PosixSourceAccessor::createAtRoot(i); + auto sourcePath = PosixSourceAccessor::createAtRoot(makeParentCanonical(i)); std::cout << fmt("%s\n", store->printStorePath(store->addToStoreSlow( baseNameOf(i), - {accessor, canonPath}, + sourcePath, method, hashAlgo).path)); } diff --git a/src/nix/add-to-store.cc b/src/nix/add-to-store.cc index 5c08f761662c..7f15de374eb9 100644 --- a/src/nix/add-to-store.cc +++ b/src/nix/add-to-store.cc @@ -37,13 +37,13 @@ struct CmdAddToStore : MixDryRun, StoreCommand { if (!namePart) namePart = baseNameOf(path); - auto [accessor, path2] = PosixSourceAccessor::createAtRoot(path); + auto sourcePath = PosixSourceAccessor::createAtRoot(makeParentCanonical(path)); auto storePath = dryRun ? store->computeStorePath( - *namePart, {accessor, path2}, caMethod, hashAlgo, {}).first + *namePart, sourcePath, caMethod, hashAlgo, {}).first : store->addToStoreSlow( - *namePart, {accessor, path2}, caMethod, hashAlgo, {}).path; + *namePart, sourcePath, caMethod, hashAlgo, {}).path; logger->cout("%s", store->printStorePath(storePath)); } diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 55aa8971e128..7189689cc917 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -938,7 +938,7 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand } continue; } else - createSymlink(target, to2); + createSymlink(target, os_string_to_string(PathViewNG { to2 })); } else throw Error("file '%s' has unsupported type", from2); diff --git a/src/nix/hash.cc b/src/nix/hash.cc index 416cd19b317c..eac421d12605 100644 --- a/src/nix/hash.cc +++ b/src/nix/hash.cc @@ -87,18 +87,35 @@ struct CmdHashBase : Command return std::make_unique(hashAlgo); }; - auto path2 = PosixSourceAccessor::createAtRoot(path); + auto makeSourcePath = [&]() -> SourcePath { + return PosixSourceAccessor::createAtRoot(makeParentCanonical(path)); + }; + Hash h { HashAlgorithm::SHA256 }; // throwaway def to appease C++ switch (mode) { case FileIngestionMethod::Flat: + { + // While usually we could use the some code as for NixArchive, + // the Flat method needs to support FIFOs, such as those + // produced by bash process substitution, e.g.: + // nix hash --mode flat <(echo hi) + // Also symlinks semantics are unambiguous in the flat case, + // so we don't need to go low-level, or reject symlink `path`s. + auto hashSink = makeSink(); + readFile(path, *hashSink); + h = hashSink->finish().first; + break; + } case FileIngestionMethod::NixArchive: { + auto sourcePath = makeSourcePath(); auto hashSink = makeSink(); - dumpPath(path2, *hashSink, (FileSerialisationMethod) mode); + dumpPath(sourcePath, *hashSink, (FileSerialisationMethod) mode); h = hashSink->finish().first; break; } case FileIngestionMethod::Git: { + auto sourcePath = makeSourcePath(); std::function hook; hook = [&](const SourcePath & path) -> git::TreeEntry { auto hashSink = makeSink(); @@ -109,7 +126,7 @@ struct CmdHashBase : Command .hash = hash, }; }; - h = hook(path2).hash; + h = hook(sourcePath).hash; break; } } diff --git a/src/nix/meson.build b/src/nix/meson.build index 1d4840b12151..2698cc873dad 100644 --- a/src/nix/meson.build +++ b/src/nix/meson.build @@ -4,8 +4,6 @@ project('nix', 'cpp', 'cpp_std=c++2a', # TODO(Qyriad): increase the warning level 'warning_level=1', - 'debug=true', - 'optimization=2', 'errorlogs=true', # Please print logs for tests that fail 'localstatedir=/nix/var', ], diff --git a/tests/functional/add.sh b/tests/functional/add.sh index 3b37ee7d47f1..0e6868d8f567 100755 --- a/tests/functional/add.sh +++ b/tests/functional/add.sh @@ -29,6 +29,47 @@ echo "$hash2" test "$hash1" = "sha256:$hash2" +# The contents can be accessed through a symlink, and this symlink has no effect on the hash +# https://github.com/NixOS/nix/issues/11941 +test_issue_11941() { + local expected actual + mkdir -p "$TEST_ROOT/foo/bar" && ln -s "$TEST_ROOT/foo" "$TEST_ROOT/foo-link" + + # legacy + expected=$(nix-store --add-fixed --recursive sha256 "$TEST_ROOT/foo/bar") + actual=$(nix-store --add-fixed --recursive sha256 "$TEST_ROOT/foo-link/bar") + [[ "$expected" == "$actual" ]] + actual=$(nix-store --add "$TEST_ROOT/foo-link/bar") + [[ "$expected" == "$actual" ]] + + # nix store add + actual=$(nix store add --hash-algo sha256 --mode nar "$TEST_ROOT/foo/bar") + [[ "$expected" == "$actual" ]] + + # cleanup + rm -r "$TEST_ROOT/foo" "$TEST_ROOT/foo-link" +} +test_issue_11941 + +# A symlink is added to the store as a symlink, not as a copy of the target +test_add_symlink() { + ln -s /bin "$TEST_ROOT/my-bin" + + # legacy + path=$(nix-store --add-fixed --recursive sha256 "$TEST_ROOT/my-bin") + [[ "$(readlink "$path")" == /bin ]] + path=$(nix-store --add "$TEST_ROOT/my-bin") + [[ "$(readlink "$path")" == /bin ]] + + # nix store add + path=$(nix store add --hash-algo sha256 --mode nar "$TEST_ROOT/my-bin") + [[ "$(readlink "$path")" == /bin ]] + + # cleanup + rm "$TEST_ROOT/my-bin" +} +test_add_symlink + #### New style commands clearStoreIfPossible diff --git a/tests/functional/hash-path.sh b/tests/functional/hash-path.sh index 86d782a9589a..4894ae391469 100755 --- a/tests/functional/hash-path.sh +++ b/tests/functional/hash-path.sh @@ -92,3 +92,32 @@ try2 md5 "20f3ffe011d4cfa7d72bfabef7882836" rm "$TEST_ROOT/hash-path/hello" ln -s x "$TEST_ROOT/hash-path/hello" try2 md5 "f78b733a68f5edbdf9413899339eaa4a" + +# Flat mode supports process substitution +h=$(nix hash path --mode flat --type sha256 --base32 <(printf "SMASH THE STATE")) +[[ 0d9n3r2i4m1zgy0wpqbsyabsfzgs952066bfp8gwvcg4mkr4r5g8 == "$h" ]] + +# Flat mode supports process substitution (hash file) +h=$(nix hash file --type sha256 --base32 <(printf "SMASH THE STATE")) +[[ 0d9n3r2i4m1zgy0wpqbsyabsfzgs952066bfp8gwvcg4mkr4r5g8 == "$h" ]] + +# Symlinks in the ancestry are ok and don't affect the result +mkdir -p "$TEST_ROOT/simple" "$TEST_ROOT/try/to/mess/with/it" +echo hi > "$TEST_ROOT/simple/hi" +ln -s "$TEST_ROOT/simple" "$TEST_ROOT/try/to/mess/with/it/simple-link" +h=$(nix hash path --type sha256 --base32 "$TEST_ROOT/simple/hi") +[[ 1xmr8jicvzszfzpz46g37mlpvbzjl2wpwvl2b05psipssyp1sm8h == "$h" ]] +h=$(nix hash path --type sha256 --base32 "$TEST_ROOT/try/to/mess/with/it/simple-link/hi") +[[ 1xmr8jicvzszfzpz46g37mlpvbzjl2wpwvl2b05psipssyp1sm8h == "$h" ]] + +# nix hash --mode nar does not canonicalize a symlink argument. +# Otherwise it can't generate a NAR whose root is a symlink. +# If you want to follow the symlink, pass $(realpath -s ...) instead. +ln -s /non-existent-48cujwe8ndf4as0bne "$TEST_ROOT/symlink-to-nowhere" +h=$(nix hash path --mode nar --type sha256 --base32 "$TEST_ROOT/symlink-to-nowhere") +[[ 1bl5ry3x1fcbwgr5c2x50bn572iixh4j1p6ax5isxly2ddgn8pbp == "$h" ]] # manually verified hash +if [[ -e /bin ]]; then + ln -s /bin "$TEST_ROOT/symlink-to-bin" + h=$(nix hash path --mode nar --type sha256 --base32 "$TEST_ROOT/symlink-to-bin") + [[ 0z2mdmkd43l0ijdxfbj1y8vzli15yh9b09n3a3rrygmjshbyypsw == "$h" ]] # manually verified hash +fi diff --git a/tests/functional/lang/eval-fail-string-nul-1.err.exp b/tests/functional/lang/eval-fail-string-nul-1.err.exp new file mode 100644 index 000000000000..2dfbea0635c8 Binary files /dev/null and b/tests/functional/lang/eval-fail-string-nul-1.err.exp differ diff --git a/tests/functional/lang/eval-fail-string-nul-1.nix b/tests/functional/lang/eval-fail-string-nul-1.nix new file mode 100644 index 000000000000..368940917113 Binary files /dev/null and b/tests/functional/lang/eval-fail-string-nul-1.nix differ diff --git a/tests/functional/lang/eval-fail-string-nul-2.err.exp b/tests/functional/lang/eval-fail-string-nul-2.err.exp new file mode 100644 index 000000000000..b1cae5325d90 Binary files /dev/null and b/tests/functional/lang/eval-fail-string-nul-2.err.exp differ diff --git a/tests/functional/lang/eval-fail-string-nul-2.nix b/tests/functional/lang/eval-fail-string-nul-2.nix new file mode 100644 index 000000000000..fd6b3258a5e4 Binary files /dev/null and b/tests/functional/lang/eval-fail-string-nul-2.nix differ diff --git a/tests/functional/meson.build b/tests/functional/meson.build index 933595cd5eff..83e08c4f5ad0 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -4,8 +4,6 @@ project('nix-functional-tests', 'cpp_std=c++2a', # TODO(Qyriad): increase the warning level 'warning_level=1', - 'debug=true', - 'optimization=2', 'errorlogs=true', # Please print logs for tests that fail ], meson_version : '>= 1.3', diff --git a/tests/nixos/default.nix b/tests/nixos/default.nix index a9a438a321b3..8e0cb1b225ba 100644 --- a/tests/nixos/default.nix +++ b/tests/nixos/default.nix @@ -159,6 +159,8 @@ in functional_root = runNixOSTestFor "x86_64-linux" ./functional/as-root.nix; + functional_symlinked-home = runNixOSTestFor "x86_64-linux" ./functional/symlinked-home.nix; + user-sandboxing = runNixOSTestFor "x86_64-linux" ./user-sandboxing; s3-binary-cache-store = runNixOSTestFor "x86_64-linux" ./s3-binary-cache-store.nix; diff --git a/tests/nixos/functional/symlinked-home.nix b/tests/nixos/functional/symlinked-home.nix new file mode 100644 index 000000000000..57c45d5d5920 --- /dev/null +++ b/tests/nixos/functional/symlinked-home.nix @@ -0,0 +1,36 @@ +/** + This test runs the functional tests on a NixOS system where the home directory + is symlinked to another location. + + The purpose of this test is to find cases where Nix uses low-level operations + that don't support symlinks on paths that include them. + + It is not a substitute for more intricate, use case-specific tests, but helps + catch common issues. +*/ +# TODO: add symlinked tmpdir +{ ... }: +{ + name = "functional-tests-on-nixos_user_symlinked-home"; + + imports = [ ./common.nix ]; + + nodes.machine = { + users.users.alice = { isNormalUser = true; }; + }; + + testScript = '' + machine.wait_for_unit("multi-user.target") + with subtest("prepare symlinked home"): + machine.succeed(""" + ( + set -x + mv /home/alice /home/alice.real + ln -s alice.real /home/alice + ) 1>&2 + """) + machine.succeed(""" + su --login --command "run-test-suite" alice >&2 + """) + ''; +}