class ChildProcess::Windows::ProcessBuilder

Attributes

cwd[RW]
detach[RW]
duplex[RW]
environment[RW]
stderr[RW]
stdin[R]
stdout[RW]

Public Class Methods

new(args) click to toggle source
# File lib/childprocess/windows/process_builder.rb, line 7
def initialize(args)
  @args        = args

  @detach      = false
  @duplex      = false
  @environment = nil
  @cwd         = nil

  @stdout      = nil
  @stderr      = nil
  @stdin       = nil

  @flags       = 0
  @job_ptr     = nil
  @cmd_ptr     = nil
  @env_ptr     = nil
  @cwd_ptr     = nil
end

Public Instance Methods

start() click to toggle source
# File lib/childprocess/windows/process_builder.rb, line 26
def start
  create_command_pointer
  create_environment_pointer
  create_cwd_pointer

  setup_flags
  setup_io

  pid = create_process
  close_handles

  pid
end

Private Instance Methods

close_handles() click to toggle source
# File lib/childprocess/windows/process_builder.rb, line 151
def close_handles
  Lib.close_handle process_info[:hProcess]
  Lib.close_handle process_info[:hThread]

  if @duplex
    @stdin = Lib.io_for(Lib.duplicate_handle(@write_pipe), File::WRONLY)
    Lib.close_handle @read_pipe
    Lib.close_handle @write_pipe
  end
end
create_command_pointer() click to toggle source
# File lib/childprocess/windows/process_builder.rb, line 42
def create_command_pointer
  string = @args.map { |arg| quote_if_necessary(arg.to_s) }.join ' '
  @cmd_ptr = FFI::MemoryPointer.from_string string
end
create_cwd_pointer() click to toggle source
# File lib/childprocess/windows/process_builder.rb, line 69
def create_cwd_pointer
  @cwd_ptr = FFI::MemoryPointer.from_string(@cwd || Dir.pwd)
end
create_environment_pointer() click to toggle source
# File lib/childprocess/windows/process_builder.rb, line 47
def create_environment_pointer
  return unless @environment.kind_of?(Hash) && @environment.any?

  strings = []

  ENV.to_hash.merge(@environment).each do |key, val|
    next if val.nil?

    if key.to_s =~ /=|\0/ || val.to_s.include?("\0")
      raise InvalidEnvironmentVariable, "#{key.inspect} => #{val.inspect}"
    end

    strings << "#{key}=#{val}\0"
  end

  strings << "\0" # terminate the env block
  env_str = strings.join

  @env_ptr = FFI::MemoryPointer.new(:long, env_str.bytesize)
  @env_ptr.put_bytes 0, env_str, 0, env_str.bytesize
end
create_process() click to toggle source
# File lib/childprocess/windows/process_builder.rb, line 73
def create_process
  ok = Lib.create_process(
    nil,          # application name
    @cmd_ptr,     # command line
    nil,          # process attributes
    nil,          # thread attributes
    true,         # inherit handles
    @flags,       # creation flags
    @env_ptr,     # environment
    @cwd_ptr,     # current directory
    startup_info, # startup info
    process_info  # process info
  )

  ok or raise LaunchError, Lib.last_error_message

  process_info[:dwProcessId]
end
process_info() click to toggle source
# File lib/childprocess/windows/process_builder.rb, line 96
def process_info
  @process_info ||= ProcessInfo.new
end
quote_if_necessary(str) click to toggle source
# File lib/childprocess/windows/process_builder.rb, line 162
def quote_if_necessary(str)
  quote = str.start_with?('"') ? "'" : '"'

  case str
  when /[\s\'"]/
    [quote, str, quote].join
  else
    str
  end
end
setup_flags() click to toggle source
# File lib/childprocess/windows/process_builder.rb, line 100
def setup_flags
  @flags |= DETACHED_PROCESS if @detach
end
setup_io() click to toggle source
# File lib/childprocess/windows/process_builder.rb, line 104
def setup_io
  startup_info[:dwFlags] ||= 0
  startup_info[:dwFlags] |= STARTF_USESTDHANDLES

  if @stdout
    startup_info[:hStdOutput] = std_stream_handle_for(@stdout)
  end

  if @stderr
    startup_info[:hStdError] = std_stream_handle_for(@stderr)
  end

  if @duplex
    read_pipe_ptr  = FFI::MemoryPointer.new(:pointer)
    write_pipe_ptr = FFI::MemoryPointer.new(:pointer)
    sa             = SecurityAttributes.new(:inherit => true)

    ok = Lib.create_pipe(read_pipe_ptr, write_pipe_ptr, sa, 0)
    Lib.check_error ok

    @read_pipe  = read_pipe_ptr.read_pointer
    @write_pipe = write_pipe_ptr.read_pointer

    Lib.set_handle_inheritance @read_pipe, true
    Lib.set_handle_inheritance @write_pipe, false

    startup_info[:hStdInput] = @read_pipe
  else
    startup_info[:hStdInput] = std_stream_handle_for(STDIN)
  end
end
startup_info() click to toggle source
# File lib/childprocess/windows/process_builder.rb, line 92
def startup_info
  @startup_info ||= StartupInfo.new
end
std_stream_handle_for(io) click to toggle source
# File lib/childprocess/windows/process_builder.rb, line 136
def std_stream_handle_for(io)
  handle = Lib.handle_for(io)

  begin
    Lib.set_handle_inheritance handle, true
  rescue ChildProcess::Error
    # If the IO was set to close on exec previously, this call will fail.
    # That's probably OK, since the user explicitly asked for it to be
    # closed (at least I have yet to find other cases where this will
    # happen...)
  end

  handle
end