From d9c51ec4e5919245dcc5e9f2f5532f4d85be5218 Mon Sep 17 00:00:00 2001 From: Qyriad Date: Fri, 12 Jul 2024 16:15:33 -0600 Subject: [PATCH 1/2] libutil: implement a realPath() utility Just a wrapper around POSIX realpath(). Change-Id: I2593770285dbae573eace490efce5b272b00b001 --- src/libutil/file-system.cc | 19 +++++++++++++++++++ src/libutil/file-system.hh | 16 ++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/libutil/file-system.cc b/src/libutil/file-system.cc index 278e5187c..f0199d36f 100644 --- a/src/libutil/file-system.cc +++ b/src/libutil/file-system.cc @@ -1,4 +1,5 @@ #include +#include #include #include @@ -106,6 +107,24 @@ Path canonPath(PathView path, bool resolveSymlinks) return s.empty() ? "/" : std::move(s); } +Path realPath(Path const & path) +{ + // With nullptr, realpath() malloc's and returns a new c-string. + char * resolved = realpath(path.c_str(), nullptr); + int saved = errno; + if (resolved == nullptr) { + throw SysError(saved, "cannot get realpath for '%s'", path); + } + + Finally const _free([&] { free(resolved); }); + + // There's not really a from_raw_parts() for std::string. + // The copy is not a big deal. + Path ret(resolved); + + return ret; +} + void chmodPath(const Path & path, mode_t mode) { if (chmod(path.c_str(), mode) == -1) diff --git a/src/libutil/file-system.hh b/src/libutil/file-system.hh index 636c13f13..e49323e84 100644 --- a/src/libutil/file-system.hh +++ b/src/libutil/file-system.hh @@ -46,6 +46,22 @@ Path absPath(Path path, */ Path canonPath(PathView path, bool resolveSymlinks = false); +/** + * Resolves a file path to a fully absolute path with no symbolic links. + * + * @param path The path to resolve. If it is relative, it will be resolved relative + * to the process's current directory. + * + * @note This is not a pure function; it performs this resolution by querying + * the filesystem. + * + * @note @ref path sadly must be (a reference to) an owned string, as std::string_view + * are not valid C strings... + * + * @return The fully resolved path. + */ +Path realPath(Path const & path); + /** * Change the permissions of a path * Not called `chmod` as it shadows and could be confused with From ae7eab49b952895b3aa88192768843bccabe1ea2 Mon Sep 17 00:00:00 2001 From: Qyriad Date: Fri, 12 Jul 2024 16:17:54 -0600 Subject: [PATCH 2/2] nix3-upgrade-nix: always use the /new/ nix-env to perform the installation Fixes #411. Change-Id: I8d87c0e9295deea26ff33234e15ee33cc68ab303 --- src/nix/upgrade-nix.cc | 31 ++++++++++++++++--------------- tests/nixos/nix-upgrade-nix.nix | 16 +++++++--------- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/src/nix/upgrade-nix.cc b/src/nix/upgrade-nix.cc index 371879791..15c5960d4 100644 --- a/src/nix/upgrade-nix.cc +++ b/src/nix/upgrade-nix.cc @@ -97,12 +97,19 @@ struct CmdUpgradeNix : MixDryRun, EvalCommand store->ensurePath(storePath); } + // {profileDir}/bin/nix-env is a symlink to {profileDir}/bin/nix, which *then* + // is a symlink to /nix/store/meow-nix/bin/nix. + // We want /nix/store/meow-nix/bin/nix-env. + Path const oldNixInStore = realPath(canonProfileDir + "/bin/nix"); + Path const oldNixEnv = dirOf(oldNixInStore) + "/nix-env"; + + Path const newNixEnv = store->printStorePath(storePath) + "/bin/nix-env"; + { Activity act(*logger, lvlInfo, actUnknown, fmt("verifying that '%s' works...", store->printStorePath(storePath))); - auto program = store->printStorePath(storePath) + "/bin/nix-env"; - auto s = runProgram(program, false, {"--version"}); + auto s = runProgram(newNixEnv, false, {"--version"}); if (s.find("Nix") == std::string::npos) - throw Error("could not verify that '%s' works", program); + throw Error("could not verify that '%s' works", newNixEnv); } logger->pause(); @@ -110,23 +117,17 @@ struct CmdUpgradeNix : MixDryRun, EvalCommand auto const fullStorePath = store->printStorePath(storePath); if (pathExists(canonProfileDir + "/manifest.nix")) { - - // {settings.nixBinDir}/nix-env is a symlink to a {settings.nixBinDir}/nix, which *then* - // is a symlink to /nix/store/meow-nix/bin/nix. We want /nix/store/meow-nix/bin/nix-env. - Path const nixInStore = canonPath(settings.nixBinDir + "/nix-env", true); - Path const nixEnvCmd = dirOf(nixInStore) + "/nix-env"; - - // First remove the existing Nix, then use that Nix by absolute path to + // First remove the existing Nix, then use the *new* Nix by absolute path to // install the new one, in case the new and old versions aren't considered // to be "the same package" by nix-env's logic (e.g., if their pnames differ). Strings removeArgs = { "--uninstall", - nixEnvCmd, + oldNixEnv, "--profile", this->profileDir, }; - printTalkative("running %s %s", nixEnvCmd, concatStringsSep(" ", removeArgs)); - runProgram(nixEnvCmd, false, removeArgs); + printTalkative("running %s %s", newNixEnv, concatStringsSep(" ", removeArgs)); + runProgram(newNixEnv, false, removeArgs); Strings upgradeArgs = { "--profile", @@ -136,8 +137,8 @@ struct CmdUpgradeNix : MixDryRun, EvalCommand "--no-sandbox", }; - printTalkative("running %s %s", nixEnvCmd, concatStringsSep(" ", upgradeArgs)); - runProgram(nixEnvCmd, false, upgradeArgs); + printTalkative("running %s %s", newNixEnv, concatStringsSep(" ", upgradeArgs)); + runProgram(newNixEnv, false, upgradeArgs); } else if (pathExists(canonProfileDir + "/manifest.json")) { this->upgradeNewStyleProfile(store, storePath); } else { diff --git a/tests/nixos/nix-upgrade-nix.nix b/tests/nixos/nix-upgrade-nix.nix index acccc2e03..01e641d81 100644 --- a/tests/nixos/nix-upgrade-nix.nix +++ b/tests/nixos/nix-upgrade-nix.nix @@ -35,9 +35,8 @@ in { machine.succeed("nix --version >&2") - # Install Lix into the default profile, overriding /run/current-system/sw/bin/nix, - # and thus making Lix think we're not on NixOS. - machine.succeed("nix-env --install '${lib.getBin lix}' --profile /nix/var/nix/profiles/default >&2") + # Use Lix to install CppNix into the default profile, overriding /run/current-system/sw/bin/nix + machine.succeed("nix-env --install '${lib.getBin newNix}' --profile /nix/var/nix/profiles/default") # Make sure that correctly got inserted into our PATH. default_profile_nix_path = machine.succeed("command -v nix") @@ -45,16 +44,15 @@ in { assert default_profile_nix_path.strip() == "/nix/var/nix/profiles/default/bin/nix", \ f"{default_profile_nix_path.strip()=} != /nix/var/nix/profiles/default/bin/nix" - # And that it's the Nix we specified. + # And that it's the Nix we specified default_profile_version = machine.succeed("nix --version") - assert "${lixVersion}" in default_profile_version, f"${lixVersion} not in {default_profile_version}" + assert "${newNixVersion}" in default_profile_version, f"${newNixVersion} not in {default_profile_version}" - # Upgrade to a different version of Nix, and make sure that also worked. - - machine.succeed("nix upgrade-nix --store-path ${newNix} >&2") + # Now upgrade to Lix, and make sure that worked. + machine.succeed("${lib.getExe lix} upgrade-nix --debug --store-path ${lix} 2>&1") default_profile_version = machine.succeed("nix --version") print(default_profile_version) - assert "${newNixVersion}" in default_profile_version, f"${newNixVersion} not in {default_profile_version}" + assert "${lixVersion}" in default_profile_version, f"${lixVersion} not in {default_profile_version}" # Now 'break' this profile -- use nix profile on it so nix-env will no longer work on it. machine.succeed(