From b51115dac52a5dfe4b0f28ecbae356bc3d502b04 Mon Sep 17 00:00:00 2001 From: Mac Malone Date: Tue, 15 Apr 2025 20:25:32 -0400 Subject: [PATCH] feat: `IO.Process.SpawnArgs.inheritEnv` (#6081) This PR adds an `inheritEnv` field to `IO.Process.SpawnArgs`. If `false`, the spawned process does not inherit its parent's environment. For example, Lake will make use of this to ensure that build processes do not use environment variables that Lake is not properly tracking with its traces. --- src/Init/System/IO.lean | 2 + src/runtime/process.cpp | 83 +++++++++++++++++++++++++---------------- 2 files changed, 53 insertions(+), 32 deletions(-) diff --git a/src/Init/System/IO.lean b/src/Init/System/IO.lean index 5666a25d8f..5dbaad1fa6 100644 --- a/src/Init/System/IO.lean +++ b/src/Init/System/IO.lean @@ -1355,6 +1355,8 @@ structure SpawnArgs extends StdioConfig where and `some` sets the variable to the new value, adding it if necessary. Variables are processed from left to right. -/ env : Array (String × Option String) := #[] + /-- Inherit environment variables from the spawning process. -/ + inheritEnv : Bool := true /-- Starts the child process in a new session and process group using `setsid`. Currently a no-op on non-POSIX platforms. diff --git a/src/runtime/process.cpp b/src/runtime/process.cpp index 50b8bc3907..ed8d63d41c 100644 --- a/src/runtime/process.cpp +++ b/src/runtime/process.cpp @@ -172,7 +172,7 @@ static void setup_stdio(SECURITY_ATTRIBUTES * saAttr, HANDLE * theirs, object ** // This code is adapted from: https://msdn.microsoft.com/en-us/library/windows/desktop/ms682499(v=vs.85).aspx static obj_res spawn(string_ref const & proc_name, array_ref const & args, stdio stdin_mode, stdio stdout_mode, stdio stderr_mode, option_ref const & cwd, array_ref>> const & env, - bool _do_setsid) { + bool inherit_env, bool _do_setsid) { HANDLE child_stdin = GetStdHandle(STD_INPUT_HANDLE); HANDLE child_stdout = GetStdHandle(STD_OUTPUT_HANDLE); HANDLE child_stderr = GetStdHandle(STD_ERROR_HANDLE); @@ -229,38 +229,45 @@ static obj_res spawn(string_ref const & proc_name, array_ref const & std::unique_ptr new_env(nullptr); - if (env.size()) { - auto * esp = GetEnvironmentStrings(); - - std::unordered_map new_env_vars; // C++17 gives us no-copy std::string_view for this, much better! - for (auto & entry : env) { - new_env_vars[entry.fst().data()] = entry.snd() ? entry.snd().get()->data() : std::string{}; - } + if (env.size() || !inherit_env) { static constexpr auto env_buf_size = 0x7fff; // according to MS docs 0x7fff is the max total size of env block new_env = std::make_unique(env_buf_size); - // First copy old evars not in new evars. - char *new_envp = new_env.get(), *key_begin = esp; - while (*key_begin) { - char *key_end = strchr(key_begin, '='); - char *entry_end = key_end + strlen(key_end); - if (!new_env_vars.count({key_begin, key_end})) { - new_envp = std::copy(key_begin, entry_end + 1, new_envp); - } - key_begin = entry_end + 1; - } - // Then copy new evars if nonempty - for(const auto & ev : new_env_vars) { - if (ev.second.empty()) continue; - // Check if the destination buffer has enough room. - if (new_envp + ev.first.length() + 1 + ev.second.length() + 1 > new_env.get() + env_buf_size - 1) break; - new_envp = std::copy(ev.first.cbegin(), ev.first.cend(), new_envp); - *new_envp++ = '='; - new_envp = std::copy(ev.second.cbegin(), ev.second.cend(), new_envp); - *new_envp++ = '\0'; - } - *new_envp = '\0'; + char *new_envp = new_env.get(); - FreeEnvironmentStrings(esp); + if (env.size()) { + std::unordered_map new_env_vars; // C++17 gives us no-copy std::string_view for this, much better! + for (auto & entry : env) { + new_env_vars[entry.fst().data()] = entry.snd() ? entry.snd().get()->data() : std::string{}; + } + + // First copy old evars not in new evars. + if (inherit_env) { + auto *esp = GetEnvironmentStrings(); + char *key_begin = esp; + while (*key_begin) { + char *key_end = strchr(key_begin, '='); + char *entry_end = key_end + strlen(key_end); + if (!new_env_vars.count({key_begin, key_end})) { + new_envp = std::copy(key_begin, entry_end + 1, new_envp); + } + key_begin = entry_end + 1; + } + FreeEnvironmentStrings(esp); + } + + // Then copy new evars if nonempty + for(const auto & ev : new_env_vars) { + if (ev.second.empty()) continue; + // Check if the destination buffer has enough room. + if (new_envp + ev.first.length() + 1 + ev.second.length() + 1 > new_env.get() + env_buf_size - 1) break; + new_envp = std::copy(ev.first.cbegin(), ev.first.cend(), new_envp); + *new_envp++ = '='; + new_envp = std::copy(ev.second.cbegin(), ev.second.cend(), new_envp); + *new_envp++ = '\0'; + } + } + + *new_envp = '\0'; } // Create the child process. @@ -423,9 +430,13 @@ static optional setup_stdio(stdio cfg) { lean_unreachable(); } +#ifdef __APPLE__ +extern "C" char **environ; +#endif + static obj_res spawn(string_ref const & proc_name, array_ref const & args, stdio stdin_mode, stdio stdout_mode, stdio stderr_mode, option_ref const & cwd, array_ref>> const & env, - bool do_setsid) { + bool inherit_env, bool do_setsid) { /* Setup stdio based on process configuration. */ auto stdin_pipe = setup_stdio(stdin_mode); auto stdout_pipe = setup_stdio(stdout_mode); @@ -434,6 +445,13 @@ static obj_res spawn(string_ref const & proc_name, array_ref const & int pid = fork(); if (pid == 0) { + if (!inherit_env) { +#ifdef __APPLE__ + environ = NULL; +#else + clearenv(); +#endif + } for (auto & entry : env) { if (entry.snd()) { setenv(entry.fst().data(), entry.snd().get()->data(), true); @@ -549,7 +567,8 @@ extern "C" LEAN_EXPORT obj_res lean_io_process_spawn(obj_arg args_, obj_arg) { stderr_mode, cnstr_get_ref_t>(args, 3), cnstr_get_ref_t>>>(args, 4), - cnstr_get_uint8(args.raw(), 5 * sizeof(object *))); + cnstr_get_uint8(args.raw(), 5 * sizeof(object *)), + cnstr_get_uint8(args.raw(), 5 * sizeof(object *) + 1)); } catch (int err) { return lean_io_result_mk_error(decode_io_error(err, nullptr)); } catch (std::system_error const & err) {