From 68ef9676c73737c8dc87de621ee3e0b24a2e0b1e Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Thu, 9 Jul 2015 16:04:06 -0600 Subject: [PATCH] Jailbreak out of the subprocess and restore original environment --- lib/vagrant/util/subprocess.rb | 99 ++++++++++++++----- .../v2/other/environmental-variables.html.md | 13 +++ 2 files changed, 86 insertions(+), 26 deletions(-) diff --git a/lib/vagrant/util/subprocess.rb b/lib/vagrant/util/subprocess.rb index b64496551..04cf82dd3 100644 --- a/lib/vagrant/util/subprocess.rb +++ b/lib/vagrant/util/subprocess.rb @@ -72,31 +72,26 @@ module Vagrant process.io.stderr = stderr_writer process.duplex = true - # Reset the Bundler environment back - this is required for anyone who - # is not using the official Vagrant installers and is running Vagrant - # via bundler - if defined?(Bundler::ORIGINAL_ENV) - Bundler::ORIGINAL_ENV.each do |k, v| - process.environment[k] = v - end - end - - # If we're in an installer on Mac and we're executing a command - # in the installer context, then force DYLD_LIBRARY_PATH to look - # at our libs first. - if Vagrant.in_installer? && Platform.darwin? + # Special installer-related things + if Vagrant.in_installer? installer_dir = ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"].to_s.downcase - if @command[0].downcase.include?(installer_dir) - @logger.info("Command in the installer. Specifying DYLD_LIBRARY_PATH...") - process.environment["DYLD_LIBRARY_PATH"] = - "#{installer_dir}/lib:#{ENV["DYLD_LIBRARY_PATH"]}" - else - @logger.debug("Command not in installer, not touching env vars.") - end - if File.setuid?(@command[0]) || File.setgid?(@command[0]) - @logger.info("Command is setuid/setgid, clearing DYLD_LIBRARY_PATH") - process.environment["DYLD_LIBRARY_PATH"] = "" + # If we're in an installer on Mac and we're executing a command + # in the installer context, then force DYLD_LIBRARY_PATH to look + # at our libs first. + if Platform.darwin? + if @command[0].downcase.include?(installer_dir) + @logger.info("Command in the installer. Specifying DYLD_LIBRARY_PATH...") + process.environment["DYLD_LIBRARY_PATH"] = + "#{installer_dir}/lib:#{ENV["DYLD_LIBRARY_PATH"]}" + else + @logger.debug("Command not in installer, not touching env vars.") + end + + if File.setuid?(@command[0]) || File.setgid?(@command[0]) + @logger.info("Command is setuid/setgid, clearing DYLD_LIBRARY_PATH") + process.environment["DYLD_LIBRARY_PATH"] = "" + end end # If the command that is being run is not inside the installer, reset @@ -105,10 +100,10 @@ module Vagrant # and $GEM_PATH for example) if !@command[0].downcase.include?(installer_dir) @logger.info("Command not in installer, restoring original environment...") - Vagrant.original_env.each do |k, v| - process.environemnt[k] = v - end + jailbreak(process.environment) end + else + jailbreak(process.environment) end # Set the environment on the process if we must @@ -259,6 +254,58 @@ module Vagrant @stderr = stderr end end + + private + + # This is, quite possibly, the saddest function in all of Vagrant. + # + # If a user is running Vagrant via Bundler (but not via the official + # installer), we want to reset to the "original" environment so that when + # shelling out to other Ruby processes (specifically), the original + # environment is restored. This is super important for things like + # rbenv and chruby, who rely on environment variables to locate gems, but + # Bundler stomps on those environment variables like an angry T-Rex after + # watching Jurassic Park 2 and realizing they replaced you with CGI. + # + # If a user is running in Vagrant via the official installer, BUT trying + # to execute a subprocess *outside* of the installer, we want to reset to + # the "original" environment. In this case, the Vagrant installer actually + # knows what the original environment was and replaces it completely. + # + # Finally, we reset any Bundler-specific environment variables, since the + # subprocess being called could, itself, be Bundler. And Bundler does not + # behave very nicely in these circumstances. + # + # This function was added in Vagrant 1.7.3, but there is a failsafe + # because the author doesn't trust himself that this functionality won't + # break existing assumptions, so users can specify + # `VAGRANT_SKIP_SUBPROCESS_JAILBREAK` and none of the above will happen. + # + # This function modifies the given hash in place! + # + # @return [nil] + def jailbreak(env = {}) + return if ENV.key?("VAGRANT_SKIP_SUBPROCESS_JAILBREAK") + + env.replace(::Bundler::ORIGINAL_ENV) if defined?(::Bundler::ORIGINAL_ENV) + env.merge!(Vagrant.original_env) + + # Bundler does this, so I guess we should as well, since I think it + # other subprocesses that use Bundler will reload it + env["MANPATH"] = ENV["BUNDLE_ORIG_MANPATH"] + + # Replace all current environment BUNDLE_ variables to nil + ENV.each do |k,_| + env[k] = nil if k[0,7] == "BUNDLE_" + end + + # If RUBYOPT was set, unset it with Bundler + if ENV.key?("RUBYOPT") + env["RUBYOPT"] = ENV["RUBYOPT"].sub("-rbundler/setup", "") + end + + nil + end end end end diff --git a/website/docs/source/v2/other/environmental-variables.html.md b/website/docs/source/v2/other/environmental-variables.html.md index b42e6774d..41ecdd252 100644 --- a/website/docs/source/v2/other/environmental-variables.html.md +++ b/website/docs/source/v2/other/environmental-variables.html.md @@ -90,6 +90,19 @@ Note that any `vagrant plugin` commands automatically don't load any plugins, so if you do install any unstable plugins, you can always use the `vagrant plugin` commands without having to worry. +## VAGRANT\_SKIP\_SUBPROCESS\_JAILBREAK + +As of Vagrant 1.7.3, Vagrant tries to intelligently detect if it is running in +the installer or running via Bundler. Although not officially supported, Vagrant +tries its best to work when executed via Bundler. When Vagrant detects that you +have spawned a subprocess that lives outside of Vagrant's installer, Vagrant +will do its best to reset the preserved environment dring the subprocess +execution. + +If Vagrant detects it is running outside of the officially installer, the +original environment will always be restored. You can disable this automatic +jailbreak by setting the `VAGRANT_SKIP_SUBPROCES_JAILBREAK`. + ## VAGRANT\_VAGRANTFILE This specifies the filename of the Vagrantfile that Vagrant searches for.