Module: ChildProcess::Windows::Lib

Extended by:
FFI::Library
Defined in:
lib/childprocess/windows.rb,
lib/childprocess/windows/constants.rb,
lib/childprocess/windows/functions.rb

Class Method Summary collapse

Class Method Details

.create_proc(cmd, opts = {}) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/childprocess/windows/functions.rb', line 5

def self.create_proc(cmd, opts = {})
  cmd_ptr = FFI::MemoryPointer.from_string cmd
  env_ptr = environment_pointer_for(opts[:environment])

  flags   = 0
  inherit = !!opts[:inherit]

  flags |= DETACHED_PROCESS if opts[:detach]

  si = StartupInfo.new
  pi = ProcessInfo.new

  if opts[:stdout] || opts[:stderr]
    si[:dwFlags] ||= 0
    si[:dwFlags] |= STARTF_USESTDHANDLES
    inherit = true

    si[:hStdOutput] = handle_for(opts[:stdout].fileno) if opts[:stdout]
    si[:hStdError]  = handle_for(opts[:stderr].fileno) if opts[:stderr]
  end

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

    ok = create_pipe(read_pipe_ptr, write_pipe_ptr, sa, 0)
    ok or raise Error, last_error_message

    read_pipe = read_pipe_ptr.read_pointer
    write_pipe = write_pipe_ptr.read_pointer

    ok = set_handle_information(write_pipe.address, HANDLE_FLAG_INHERIT, 0)
    ok or raise Error, last_error_message

    si[:hStdInput] = read_pipe
  end

  ok = create_process(
    nil,      # application name
    cmd_ptr,  # command line
    nil,      # process attributes
    nil,      # thread attributes
    inherit,  # inherit handles
    flags,    # creation flags
    env_ptr,  # environment
    nil,      # current directory
    si,       # startup info
    pi        # process info
  )

  ok or raise LaunchError, last_error_message

  close_handle pi[:hProcess]
  close_handle pi[:hThread]

  if opts[:duplex]
    opts[:stdin] = io_for(duplicate_handle(write_pipe), File::WRONLY)
    close_handle read_pipe
    close_handle write_pipe
  end

  pi[:dwProcessId]
end

.duplicate_handle(handle) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/childprocess/windows/functions.rb', line 119

def self.duplicate_handle(handle)
  dup  = FFI::MemoryPointer.new(:pointer)
  proc = current_process

  ok = _duplicate_handle(
    proc, handle, proc, dup, 0, false, DUPLICATE_SAME_ACCESS)

  ok or raise Error, last_error_message

  dup.read_pointer
ensure
  close_handle proc
end

.environment_pointer_for(env) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/childprocess/windows/functions.rb', line 133

def self.environment_pointer_for(env)
  return unless env.kind_of?(Hash) && env.any?

  strings = ENV.map { |k,v| "#{k}=#{v}\0" }
  env.each do |key, value|
    if key.include?("=")
      raise InvalidEnvironmentVariableName, key
    end

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

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

  ptr = FFI::MemoryPointer.new(:long, env_str.bytesize)
  ptr.write_bytes env_str, 0, env_str.bytesize

  ptr
end

.handle_for(fd_or_io) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/childprocess/windows/functions.rb', line 83

def self.handle_for(fd_or_io)
  case fd_or_io
  when IO
    handle = get_osfhandle(fd_or_io.fileno)
  when Fixnum
    handle = get_osfhandle(fd_or_io)
  else
    if fd_or_io.respond_to?(:to_io)
      io = fd_or_io.to_io

      unless io.kind_of?(IO)
        raise TypeError, "expected #to_io to return an instance of IO"
      end

      handle = get_osfhandle(io.fileno)
    else
      raise TypeError, "invalid type: #{fd_or_io.inspect}"
    end
  end

  if handle == INVALID_HANDLE_VALUE
    raise Error, last_error_message
  end

  handle
end

.io_for(handle, flags = File::RDONLY) ⇒ Object



110
111
112
113
114
115
116
117
# File 'lib/childprocess/windows/functions.rb', line 110

def self.io_for(handle, flags = File::RDONLY)
  fd = open_osfhandle(handle, flags)
  if fd == -1
    raise Error, last_error_message
  end

  ::IO.for_fd fd, flags
end

.last_error_messageObject



70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/childprocess/windows/functions.rb', line 70

def self.last_error_message
  errnum = get_last_error
  buf = FFI::MemoryPointer.new :char, 512

  size = format_message(
    FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
    nil, errnum, 0, buf, buf.size, nil
  )

  str = buf.read_string(size).strip
  "#{str} (#{errnum})"
end

.msvcrt_nameObject



9
10
11
12
13
14
15
16
17
18
# File 'lib/childprocess/windows.rb', line 9

def self.msvcrt_name
  host_part = RbConfig::CONFIG['host_os'].split("_")[1]
  manifest  = File.join(RbConfig::CONFIG['bindir'], 'ruby.exe.manifest')

  if host_part && host_part.to_i > 80 && File.exists?(manifest)
    "msvcr#{host_part}"
  else
    "msvcrt"
  end
end