diff --git a/lib/vagrant/ssh.rb b/lib/vagrant/ssh.rb index 8688544b8..c11324709 100644 --- a/lib/vagrant/ssh.rb +++ b/lib/vagrant/ssh.rb @@ -12,6 +12,7 @@ module Vagrant autoload :Session, 'vagrant/ssh/session' include Util::Retryable + include Util::SafeExec # Reference back up to the environment which this SSH object belongs # to @@ -56,10 +57,8 @@ module Vagrant # Some hackery going on here. On Mac OS X Leopard (10.5), exec fails # (GH-51). As a workaround, we fork and wait. On all other platforms, # we simply exec. - pid = nil - pid = fork if Util::Platform.leopard? || Util::Platform.tiger? - Kernel.exec "ssh #{command_options.join(" ")} #{options[:username]}@#{options[:host]}".strip if pid.nil? - Process.wait(pid) if pid + command = "ssh #{command_options.join(" ")} #{options[:username]}@#{options[:host]}".strip + safe_exec(command) end # Opens an SSH connection to this environment's virtual machine and yields diff --git a/lib/vagrant/util.rb b/lib/vagrant/util.rb index 5583759fe..25d592105 100644 --- a/lib/vagrant/util.rb +++ b/lib/vagrant/util.rb @@ -8,6 +8,7 @@ module Vagrant autoload :Platform, 'vagrant/util/platform' autoload :ResourceLogger, 'vagrant/util/resource_logger' autoload :Retryable, 'vagrant/util/retryable' + autoload :SafeExec, 'vagrant/util/safe_exec' autoload :StackedProcRunner, 'vagrant/util/stacked_proc_runner' autoload :TemplateRenderer, 'vagrant/util/template_renderer' end diff --git a/lib/vagrant/util/safe_exec.rb b/lib/vagrant/util/safe_exec.rb new file mode 100644 index 000000000..7ebafac7e --- /dev/null +++ b/lib/vagrant/util/safe_exec.rb @@ -0,0 +1,28 @@ +module Vagrant + module Util + # This module provies a `safe_exec` method which is a drop-in + # replacement for `Kernel.exec` which addresses a specific issue + # which manifests on OS X 10.5 and perhaps other operating systems. + # This issue causes `exec` to fail if there is more than one system + # thread. In that case, `safe_exec` automatically falls back to + # forking. + module SafeExec + def safe_exec(command) + fork_instead = false + begin + pid = nil + pid = fork if fork_instead + Kernel.exec(command) if pid.nil? + Process.wait(pid) if pid + rescue Errno::E045 + # We retried already, raise the issue and be done + raise if fork_instead + + # The error manifested itself, retry with a fork. + fork_instead = true + retry + end + end + end + end +end diff --git a/test/vagrant/ssh_test.rb b/test/vagrant/ssh_test.rb index 61dd31618..25ddd5eea 100644 --- a/test/vagrant/ssh_test.rb +++ b/test/vagrant/ssh_test.rb @@ -20,10 +20,8 @@ class SshTest < Test::Unit::TestCase mock_ssh @ssh.stubs(:check_key_permissions) @ssh.stubs(:port).returns(2222) - Kernel.stubs(:exec) + @ssh.stubs(:safe_exec) Kernel.stubs(:system).returns(true) - - Vagrant::Util::Platform.stubs(:leopard?).returns(false) end should "raise an exception if SSH is not found" do @@ -41,7 +39,7 @@ class SshTest < Test::Unit::TestCase should "check key permissions prior to exec" do exec_seq = sequence("exec_seq") @ssh.expects(:check_key_permissions).with(@env.config.ssh.private_key_path).once.in_sequence(exec_seq) - Kernel.expects(:exec).in_sequence(exec_seq) + @ssh.expects(:safe_exec).in_sequence(exec_seq) @ssh.connect end @@ -81,24 +79,6 @@ class SshTest < Test::Unit::TestCase @ssh.connect end - context "on leopard" do - setup do - Vagrant::Util::Platform.stubs(:leopard?).returns(true) - end - - teardown do - Vagrant::Util::Platform.stubs(:leopard?).returns(false) - end - - should "fork, exec, and wait" do - pid = mock("pid") - @ssh.expects(:fork).once.returns(pid) - Process.expects(:wait).with(pid) - - @ssh.connect - end - end - context "checking windows" do teardown do Mario::Platform.forced = Mario::Platform::Linux @@ -116,7 +96,7 @@ class SshTest < Test::Unit::TestCase end def ssh_exec_expect(port, key_path, uname, host) - Kernel.expects(:exec).with() do |arg| + @ssh.expects(:safe_exec).with() do |arg| assert arg =~ /^ssh/, "ssh command expected" assert arg =~ /-p #{port}/, "-p #{port} expected" assert arg =~ /-i #{key_path}/, "-i #{key_path} expected"