class Vagrant::Util::SafeExec

This module provides 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 (GH-51) 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.

Public Class Methods

exec(command, *args) click to toggle source
# File lib/vagrant/util/safe_exec.rb, line 13
def self.exec(command, *args)
  # Create a list of things to rescue from. Since this is OS
  # specific, we need to do some defined? checks here to make
  # sure they exist.
  rescue_from = []
  rescue_from << Errno::EOPNOTSUPP if defined?(Errno::EOPNOTSUPP)
  rescue_from << Errno::E045 if defined?(Errno::E045)
  rescue_from << SystemCallError

  fork_instead = false
  begin
    if fork_instead
      if Vagrant::Util::Platform.windows?
        @@logger.debug("Using subprocess because windows platform")
        args = args.dup << {notify: [:stdout, :stderr]}
        result = Vagrant::Util::Subprocess.execute(command, *args) do |type, data|
          case type
          when :stdout
            @@logger.info(data, new_line: false)
          when :stderr
            @@logger.info(data, new_line: false)
          end
        end
        Kernel.exit(result.exit_code)
      else
        pid = fork
        Kernel.exec(command, *args)
        Process.wait(pid)
      end
    else
      if Vagrant::Util::Platform.windows?
        # Re-generate strings to ensure common encoding
        @@logger.debug("Converting command and arguments to common UTF-8 encoding for exec.")
        @@logger.debug("Command: `#{command.inspect}` Args: `#{args.inspect}`")
        begin
          command = "#{command}".encode("UTF-8")
        rescue Encoding::UndefinedConversionError => e
          @@logger.warn("Failed to convert command - #{e.class}: #{e} (`#{command}`)")
        end
        args = args.map do |arg|
          begin
            "#{arg}".encode("UTF-8")
          rescue Encoding::UndefinedConversionError => e
            @@logger.warn("Failed to convert command argument - #{e.class}: #{e} (`#{arg}`)")
            arg
          end
        end
        @@logger.debug("Converted - Command: `#{command.inspect}` Args: `#{args.inspect}`")
      end
      Kernel.exec(command, *args)
    end
  rescue *rescue_from
    # 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