diff --git a/src/libfetchers/git-lfs-fetch.cc b/src/libfetchers/git-lfs-fetch.cc index 91e9e7f3894..ea23aefa295 100644 --- a/src/libfetchers/git-lfs-fetch.cc +++ b/src/libfetchers/git-lfs-fetch.cc @@ -73,11 +73,12 @@ LfsApiInfo getLfsApi(ParsedURL url) args.push_back(string_to_os_string(url.renderPath(/*encode=*/false))); args.push_back(OS_STR("download")); - auto [status, output] = runProgram({{.program = "ssh", .args = args}}); + auto [status, output] = runProgram({{.program = sshProgram(), .args = args}}); if (output.empty()) throw Error( - "git-lfs-authenticate: no output (cmd: 'ssh %s')", + "git-lfs-authenticate: no output (cmd: '%s %s')", + sshProgram().string(), concatMapStringsSep( " ", args, [](const OsString & s) { return escapeShellArgAlways(os_string_to_string(s)); })); diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index e5afbcd6fbb..1fe79ddea00 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -391,6 +391,8 @@ RunPager::RunPager() Pipe toPager; toPager.create(); + const auto & sh = shProgram(); + pid = startProcess([&]() { if (dup2(toPager.readSide.get(), STDIN_FILENO) == -1) throw SysError("dupping stdin"); @@ -398,7 +400,7 @@ RunPager::RunPager() setEnv("LESS", "FRSXMK"); restoreProcessContext(); if (pager) - execl("/bin/sh", "sh", "-c", pager, nullptr); + execl(sh.c_str(), "sh", "-c", pager, nullptr); execlp("pager", "pager", nullptr); execlp("less", "less", nullptr); execlp("more", "more", nullptr); diff --git a/src/libstore/include/nix/store/ssh.hh b/src/libstore/include/nix/store/ssh.hh index d56d1331b56..235c2c5bfb5 100644 --- a/src/libstore/include/nix/store/ssh.hh +++ b/src/libstore/include/nix/store/ssh.hh @@ -9,6 +9,13 @@ namespace nix { +/** + * The ssh program used for all ssh invocations (build-time configurable + * via the `ssh-program` option; either a name resolved via PATH or an + * absolute path). + */ +const std::filesystem::path & sshProgram(); + OsStrings getNixSshOpts(); class SSHMaster diff --git a/src/libstore/meson.options b/src/libstore/meson.options index fc55dd96273..9b44a85794c 100644 --- a/src/libstore/meson.options +++ b/src/libstore/meson.options @@ -44,7 +44,7 @@ option( 'ssh-program', type : 'string', value : 'ssh', - description : 'the ssh program used for remote stores, remote builders and store copies (a name resolved via PATH, or an absolute path)', + description : 'the ssh program used for remote stores, remote builders, store copies and Git LFS authentication (a name resolved via PATH, or an absolute path)', ) option( diff --git a/src/libstore/ssh.cc b/src/libstore/ssh.cc index da56433b344..2fb244fddf9 100644 --- a/src/libstore/ssh.cc +++ b/src/libstore/ssh.cc @@ -59,6 +59,12 @@ static void checkValidAuthority(const ParsedURL::Authority & authority) } } +const std::filesystem::path & sshProgram() +{ + static const std::filesystem::path program = SSH_PROGRAM; + return program; +} + OsStrings getNixSshOpts() { std::string sshOpts = getEnv("NIX_SSHOPTS").value_or(""); @@ -130,7 +136,7 @@ bool SSHMaster::isMasterRunning() auto res = runProgram( RunOptions{ - .spawnOptions = {.program = SSH_PROGRAM, .args = std::move(args)}, + .spawnOptions = {.program = sshProgram(), .args = std::move(args)}, .mergeStderrToStdout = true, }); return res.first == 0; @@ -138,16 +144,16 @@ bool SSHMaster::isMasterRunning() static OsStringMap createSSHEnv() { - // Copy the environment and set SHELL=/bin/sh + // Copy the environment and set SHELL to the configured sh OsStringMap env = getEnvOs(); // SSH will invoke the "user" shell for -oLocalCommand, but that means // $SHELL. To keep things simple and avoid potential issues with other - // shells, we set it to /bin/sh. + // shells, we set it to the configured POSIX shell. // Technically, we don't need that, and we could reinvoke ourselves to print // "started". Self-reinvocation is tricky with library consumers, but mostly // solved; refer to the development history of nixExePath in libstore/globals.cc. - env.insert_or_assign(OS_STR("SHELL"), OS_STR("/bin/sh")); + env.insert_or_assign(OS_STR("SHELL"), shProgram().native()); return env; } @@ -171,7 +177,7 @@ std::unique_ptr SSHMaster::startCommand(OsStrings && comm std::filesystem::path program; if (!fakeSSH) { - program = SSH_PROGRAM; + program = sshProgram(); args = {string_to_os_string(hostnameAndUser), OS_STR("-x")}; addCommonSSHOpts(args); if (!socketPath.empty()) @@ -262,7 +268,7 @@ std::filesystem::path SSHMaster::startMaster() state->sshMaster = spawnProgram( { - .program = SSH_PROGRAM, + .program = sshProgram(), .lookupPath = true, .args = std::move(args), .environment = createSSHEnv(), diff --git a/src/libutil/include/nix/util/processes.hh b/src/libutil/include/nix/util/processes.hh index 2854afa0893..0efbd93786d 100644 --- a/src/libutil/include/nix/util/processes.hh +++ b/src/libutil/include/nix/util/processes.hh @@ -129,6 +129,12 @@ struct ProcessOptions pid_t startProcess(fun processMain, const ProcessOptions & options = ProcessOptions()); #endif +/** + * The POSIX shell used to run command strings with `-c` (build-time + * configurable via the `sh-program` option; always an absolute path). + */ +const std::filesystem::path & shProgram(); + /** * Run a program and return its stdout in a string (i.e., like the * shell backtick operator). diff --git a/src/libutil/meson.build b/src/libutil/meson.build index 7c0af0bd02b..6f5b1406529 100644 --- a/src/libutil/meson.build +++ b/src/libutil/meson.build @@ -144,6 +144,16 @@ deps_public += nlohmann_json cxx = meson.get_compiler('cpp') +fs = import('fs') + +sh_program = get_option('sh-program') +if host_machine.system() != 'windows' and not fs.is_absolute(sh_program) + # sh_program is exec'd directly and handed to ssh as $SHELL, + # neither of which does a PATH lookup. It's only meaningful on Linux. + error('sh-program must be an absolute path, got \'' + sh_program + '\'') +endif +configdata_priv.set_quoted('SH_PROGRAM', sh_program) + config_priv_h = configure_file( configuration : configdata_priv, output : 'util-config-private.hh', diff --git a/src/libutil/meson.options b/src/libutil/meson.options index a8280616297..480c75e130d 100644 --- a/src/libutil/meson.options +++ b/src/libutil/meson.options @@ -5,3 +5,10 @@ option( type : 'feature', description : 'determine microarchitecture levels with libcpuid (only relevant on x86_64)', ) + +option( + 'sh-program', + type : 'string', + value : '/bin/sh', + description : 'the POSIX shell used to run command strings such as $PAGER and ssh LocalCommand (must be an absolute path)', +) diff --git a/src/libutil/processes.cc b/src/libutil/processes.cc index 68c54f9c074..37863474bee 100644 --- a/src/libutil/processes.cc +++ b/src/libutil/processes.cc @@ -2,10 +2,18 @@ #include "nix/util/serialise.hh" #include "nix/util/signals.hh" +#include "util-config-private.hh" + namespace nix { void ExecError::anchor() {} +const std::filesystem::path & shProgram() +{ + static const std::filesystem::path program = SH_PROGRAM; + return program; +} + Pid & Pid::operator=(Pid && other) noexcept { swap(*this, other); diff --git a/src/nix/env.cc b/src/nix/env.cc index 4022c3effa8..3ff760956b5 100644 --- a/src/nix/env.cc +++ b/src/nix/env.cc @@ -3,6 +3,8 @@ #include #include "nix/cmd/command.hh" + +#include "cli-config-private.hh" #include "nix/expr/eval.hh" #include "run.hh" #include "nix/util/strings.hh" @@ -37,7 +39,7 @@ struct CmdShell : InstallablesCommand, MixEnvironment using InstallablesCommand::run; - std::vector command = {getEnv("SHELL").value_or("bash")}; + std::vector command = {getEnv("SHELL").value_or(FALLBACK_BASH)}; CmdShell() { diff --git a/src/nix/meson.options b/src/nix/meson.options index 50843bfa992..a80ce75d70a 100644 --- a/src/nix/meson.options +++ b/src/nix/meson.options @@ -26,5 +26,5 @@ option( 'bash-program', type : 'string', value : 'bash', - description : 'the bash used as the fallback interactive shell for nix develop / nix-shell when bashInteractive from nixpkgs is unavailable (a name resolved via PATH, or an absolute path)', + description : 'the bash used as the fallback interactive shell for nix develop / nix-shell when bashInteractive from nixpkgs is unavailable, and for nix shell when $SHELL is unset (a name resolved via PATH, or an absolute path)', )