chef 0.10.4 → 0.10.6.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. data/distro/common/html/chef-client.8.html +9 -4
  2. data/distro/common/html/chef-expander.8.html +4 -4
  3. data/distro/common/html/chef-expanderctl.8.html +4 -4
  4. data/distro/common/html/chef-server-webui.8.html +4 -4
  5. data/distro/common/html/chef-server.8.html +4 -4
  6. data/distro/common/html/chef-solo.8.html +4 -4
  7. data/distro/common/html/chef-solr.8.html +6 -4
  8. data/distro/common/html/knife-bootstrap.1.html +13 -11
  9. data/distro/common/html/knife-client.1.html +4 -4
  10. data/distro/common/html/knife-configure.1.html +4 -4
  11. data/distro/common/html/knife-cookbook-site.1.html +7 -5
  12. data/distro/common/html/knife-cookbook.1.html +10 -8
  13. data/distro/common/html/knife-data-bag.1.html +4 -4
  14. data/distro/common/html/knife-environment.1.html +4 -4
  15. data/distro/common/html/knife-exec.1.html +4 -4
  16. data/distro/common/html/knife-index.1.html +4 -4
  17. data/distro/common/html/knife-node.1.html +5 -26
  18. data/distro/common/html/knife-role.1.html +4 -4
  19. data/distro/common/html/knife-search.1.html +9 -8
  20. data/distro/common/html/knife-ssh.1.html +10 -10
  21. data/distro/common/html/knife-status.1.html +4 -4
  22. data/distro/common/html/knife-tag.1.html +4 -4
  23. data/distro/common/html/knife.1.html +36 -10
  24. data/distro/common/html/shef.1.html +4 -4
  25. data/distro/common/man/man1/knife-bootstrap.1 +18 -10
  26. data/distro/common/man/man1/knife-client.1 +1 -1
  27. data/distro/common/man/man1/knife-configure.1 +1 -1
  28. data/distro/common/man/man1/knife-cookbook-site.1 +10 -2
  29. data/distro/common/man/man1/knife-cookbook.1 +10 -5
  30. data/distro/common/man/man1/knife-data-bag.1 +1 -1
  31. data/distro/common/man/man1/knife-environment.1 +1 -1
  32. data/distro/common/man/man1/knife-exec.1 +1 -1
  33. data/distro/common/man/man1/knife-index.1 +1 -1
  34. data/distro/common/man/man1/knife-node.1 +2 -22
  35. data/distro/common/man/man1/knife-role.1 +1 -1
  36. data/distro/common/man/man1/knife-search.1 +8 -5
  37. data/distro/common/man/man1/knife-ssh.1 +17 -12
  38. data/distro/common/man/man1/knife-status.1 +1 -1
  39. data/distro/common/man/man1/knife-tag.1 +1 -1
  40. data/distro/common/man/man1/knife.1 +50 -9
  41. data/distro/common/man/man1/shef.1 +1 -1
  42. data/distro/common/man/man8/chef-client.8 +21 -1
  43. data/distro/common/man/man8/chef-expander.8 +1 -1
  44. data/distro/common/man/man8/chef-expanderctl.8 +1 -1
  45. data/distro/common/man/man8/chef-server-webui.8 +1 -1
  46. data/distro/common/man/man8/chef-server.8 +1 -1
  47. data/distro/common/man/man8/chef-solo.8 +1 -1
  48. data/distro/common/man/man8/chef-solr.8 +9 -1
  49. data/distro/common/markdown/man1/knife-bootstrap.mkd +9 -5
  50. data/distro/common/markdown/man1/knife-cookbook-site.mkd +5 -1
  51. data/distro/common/markdown/man1/knife-cookbook.mkd +7 -4
  52. data/distro/common/markdown/man1/knife-node.mkd +1 -19
  53. data/distro/common/markdown/man1/knife-search.mkd +5 -4
  54. data/distro/common/markdown/man1/knife-ssh.mkd +8 -0
  55. data/distro/common/markdown/man1/knife.mkd +39 -8
  56. data/distro/common/markdown/man8/chef-client.mkd +10 -0
  57. data/distro/common/markdown/man8/chef-solr.mkd +5 -1
  58. data/distro/debian/etc/init.d/chef-client +48 -38
  59. data/distro/redhat/etc/init.d/chef-client +6 -2
  60. data/lib/chef/checksum.rb +9 -24
  61. data/lib/chef/checksum/storage.rb +18 -0
  62. data/lib/chef/checksum/storage/filesystem.rb +56 -0
  63. data/lib/chef/config.rb +6 -2
  64. data/lib/chef/cookbook/syntax_check.rb +1 -1
  65. data/lib/chef/cookbook_version.rb +37 -9
  66. data/lib/chef/file_access_control.rb +1 -1
  67. data/lib/chef/handler.rb +21 -0
  68. data/lib/chef/knife/bootstrap.rb +3 -1
  69. data/lib/chef/knife/bootstrap/archlinux-gems.erb +10 -0
  70. data/lib/chef/knife/bootstrap/centos5-gems.erb +13 -2
  71. data/lib/chef/knife/bootstrap/fedora13-gems.erb +10 -0
  72. data/lib/chef/knife/bootstrap/ubuntu10.04-apt.erb +10 -0
  73. data/lib/chef/knife/bootstrap/ubuntu10.04-gems.erb +10 -0
  74. data/lib/chef/knife/client_create.rb +13 -7
  75. data/lib/chef/knife/client_delete.rb +0 -2
  76. data/lib/chef/knife/client_edit.rb +0 -3
  77. data/lib/chef/knife/client_list.rb +0 -1
  78. data/lib/chef/knife/client_reregister.rb +2 -3
  79. data/lib/chef/knife/client_show.rb +0 -1
  80. data/lib/chef/knife/configure.rb +1 -1
  81. data/lib/chef/knife/configure_client.rb +0 -2
  82. data/lib/chef/knife/cookbook_create.rb +12 -12
  83. data/lib/chef/knife/cookbook_delete.rb +2 -0
  84. data/lib/chef/knife/cookbook_download.rb +9 -6
  85. data/lib/chef/knife/cookbook_list.rb +1 -6
  86. data/lib/chef/knife/cookbook_metadata.rb +8 -8
  87. data/lib/chef/knife/cookbook_site_list.rb +4 -2
  88. data/lib/chef/knife/cookbook_test.rb +1 -1
  89. data/lib/chef/knife/core/bootstrap_context.rb +9 -0
  90. data/lib/chef/knife/core/generic_presenter.rb +8 -1
  91. data/lib/chef/knife/core/node_presenter.rb +30 -0
  92. data/lib/chef/knife/core/ui.rb +8 -3
  93. data/lib/chef/knife/data_bag_create.rb +2 -5
  94. data/lib/chef/knife/data_bag_from_file.rb +2 -6
  95. data/lib/chef/knife/node_show.rb +8 -3
  96. data/lib/chef/knife/role_create.rb +2 -2
  97. data/lib/chef/knife/role_from_file.rb +12 -5
  98. data/lib/chef/knife/search.rb +1 -1
  99. data/lib/chef/knife/ssh.rb +20 -3
  100. data/lib/chef/mixin/command/windows.rb +1 -1
  101. data/lib/chef/platform.rb +24 -0
  102. data/lib/chef/provider/deploy.rb +93 -17
  103. data/lib/chef/provider/file.rb +5 -1
  104. data/lib/chef/provider/group/groupadd.rb +11 -1
  105. data/lib/chef/provider/ifconfig.rb +66 -5
  106. data/lib/chef/provider/package.rb +41 -5
  107. data/lib/chef/provider/package/apt.rb +10 -0
  108. data/lib/chef/provider/package/yum.rb +59 -14
  109. data/lib/chef/provider/remote_directory.rb +0 -1
  110. data/lib/chef/provider/service/debian.rb +2 -2
  111. data/lib/chef/provider/service/invokercd.rb +35 -0
  112. data/lib/chef/provider/service/windows.rb +92 -83
  113. data/lib/chef/resource.rb +4 -1
  114. data/lib/chef/resource/deploy.rb +9 -0
  115. data/lib/chef/resource/group.rb +8 -0
  116. data/lib/chef/resource/ifconfig.rb +12 -2
  117. data/lib/chef/resource/package.rb +1 -1
  118. data/lib/chef/resource/service.rb +1 -10
  119. data/lib/chef/shef/shef_session.rb +2 -1
  120. data/lib/chef/shell_out.rb +0 -1
  121. data/lib/chef/shell_out/windows.rb +508 -52
  122. data/lib/chef/solr_query/solr_http_request.rb +19 -5
  123. data/lib/chef/tasks/chef_repo.rake +9 -5
  124. data/lib/chef/version.rb +1 -1
  125. metadata +414 -453
@@ -405,7 +405,10 @@ F
405
405
  end
406
406
 
407
407
  def run_action(action)
408
- Chef::Log.info("Processing #{self} action #{action} (#{defined_at})")
408
+ if Chef::Config[:verbose_logging] || Chef::Log.level == :debug
409
+ # This can be noisy
410
+ Chef::Log.info("Processing #{self} action #{action} (#{defined_at})")
411
+ end
409
412
 
410
413
  # ensure that we don't leave @updated_by_last_action set to true
411
414
  # on accident
@@ -66,6 +66,7 @@ class Chef
66
66
  @revision = 'HEAD'
67
67
  @action = :deploy
68
68
  @migrate = false
69
+ @rollback_on_error = false
69
70
  @remote = "origin"
70
71
  @enable_submodules = false
71
72
  @shallow_clone = false
@@ -155,6 +156,14 @@ class Chef
155
156
  )
156
157
  end
157
158
 
159
+ def rollback_on_error(arg=nil)
160
+ set_or_return(
161
+ :rollback_on_error,
162
+ arg,
163
+ :kind_of => [ TrueClass, FalseClass ]
164
+ )
165
+ end
166
+
158
167
  def user(arg=nil)
159
168
  set_or_return(
160
169
  :user,
@@ -65,6 +65,14 @@ class Chef
65
65
  :kind_of => [ TrueClass, FalseClass ]
66
66
  )
67
67
  end
68
+
69
+ def system(arg=nil)
70
+ set_or_return(
71
+ :system,
72
+ arg,
73
+ :kind_of => [ TrueClass, FalseClass ]
74
+ )
75
+ end
68
76
  end
69
77
  end
70
78
  end
@@ -1,5 +1,5 @@
1
1
  #
2
- # Author:: Jason K. Jackson (jason.jackson@monster.com)
2
+ # Author:: Jason K. Jackson (jasonjackson@gmail.com)
3
3
  # Copyright:: Copyright (c) 2009 Jason K. Jackson
4
4
  # License:: Apache License, Version 2.0
5
5
  #
@@ -27,7 +27,7 @@ class Chef
27
27
  @resource_name = :ifconfig
28
28
  @target = name
29
29
  @action = :add
30
- @allowed_actions.push(:add, :delete)
30
+ @allowed_actions.push(:add, :delete, :enable, :disable)
31
31
  @hwaddr = nil
32
32
  @mask = nil
33
33
  @inet_addr = nil
@@ -38,6 +38,7 @@ class Chef
38
38
  @onboot = nil
39
39
  @network = nil
40
40
  @bootproto = nil
41
+ @onparent = nil
41
42
  end
42
43
 
43
44
  def target(arg=nil)
@@ -127,7 +128,16 @@ class Chef
127
128
  :kind_of => String
128
129
  )
129
130
  end
131
+
132
+ def onparent(arg=nil)
133
+ set_or_return(
134
+ :onparent,
135
+ arg,
136
+ :kind_of => String
137
+ )
138
+ end
130
139
  end
140
+
131
141
  end
132
142
  end
133
143
 
@@ -32,7 +32,7 @@ class Chef
32
32
  @source = nil
33
33
  @action = :install
34
34
  @options = nil
35
- @allowed_actions.push(:install, :upgrade, :remove, :purge)
35
+ @allowed_actions.push(:install, :upgrade, :remove, :purge, :reconfig)
36
36
  end
37
37
 
38
38
  def package_name(arg=nil)
@@ -145,16 +145,7 @@ class Chef
145
145
  @supports
146
146
  end
147
147
  end
148
-
149
- # This attribute applies for Windows only.
150
- def startup_type(arg=nil)
151
- set_or_return(
152
- :startup_type,
153
- arg,
154
- :equal_to => [:automatic, :mannual]
155
- )
156
- end
157
-
148
+
158
149
  end
159
150
  end
160
151
  end
@@ -161,10 +161,11 @@ module Shef
161
161
  end
162
162
 
163
163
  def rebuild_context
164
+ @run_status = Chef::RunStatus.new(@node)
164
165
  Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::FileSystemFileVendor.new(manifest, Chef::Config[:cookbook_path]) }
165
166
  @run_context = Chef::RunContext.new(@node, Chef::CookbookCollection.new(Chef::CookbookLoader.new(Chef::Config[:cookbook_path])))
166
167
  @run_context.load(Chef::RunList::RunListExpansionFromDisk.new("_default", []))
167
- run_status.run_context = run_context
168
+ @run_status.run_context = run_context
168
169
  end
169
170
 
170
171
  private
@@ -21,7 +21,6 @@ require 'tmpdir'
21
21
  require 'chef/log'
22
22
  require 'fcntl'
23
23
  require 'chef/exceptions'
24
- require 'chef/shell_out/unix'
25
24
 
26
25
  class Chef
27
26
 
@@ -1,5 +1,6 @@
1
1
  #--
2
2
  # Author:: Daniel DeLeo (<dan@opscode.com>)
3
+ # Author:: John Keiser (<jkeiser@opscode.com>)
3
4
  # Copyright:: Copyright (c) 2011 Opscode, Inc.
4
5
  # License:: Apache License, Version 2.0
5
6
  #
@@ -16,83 +17,538 @@
16
17
  # limitations under the License.
17
18
  #
18
19
 
19
- require 'timeout'
20
- if RUBY_VERSION =~ /^1\.8/
21
- require 'win32/open3'
22
- else
23
- require 'open3'
24
- end
20
+ require 'win32/process'
21
+ require 'windows/handle'
22
+ require 'windows/process'
23
+ require 'windows/synchronize'
25
24
 
26
25
  class Chef
27
26
  class ShellOut
28
27
  module Windows
29
28
 
29
+ include ::Windows::Handle
30
+ include ::Windows::Process
31
+ include ::Windows::Synchronize
32
+
33
+ TIME_SLICE = 0.05
34
+
30
35
  #--
31
36
  # Missing lots of features from the UNIX version, such as
32
- # environment, cwd, etc.
37
+ # uid, etc.
33
38
  def run_command
34
- # win32 open4 is really just open3.
35
- Open3.popen3(@command) do |stdin,stdout,stderr|
36
- @finished_stdout = false
37
- @finished_stderr = false
38
- stdin.close
39
- stdout.sync = true
40
- stderr.sync = true
41
-
42
- # TBH, I really don't know what this will do when it times out.
43
- # However, I'm powerless to make windows have non-blocking IO, so
44
- # thread party it is.
45
- Timeout.timeout(timeout) do
46
- out_reader = Thread.new do
47
- loop do
48
- read_stdout(stdout)
49
- break if @finished_stdout
50
- end
51
- end
52
- err_reader = Thread.new do
53
- loop do
54
- read_stderr(stderr)
55
- break if @finished_stderr
39
+
40
+ #
41
+ # Create pipes to capture stdout and stderr,
42
+ #
43
+ stdout_read, stdout_write = IO.pipe
44
+ stderr_read, stderr_write = IO.pipe
45
+ open_streams = [ stdout_read, stderr_read ]
46
+
47
+ begin
48
+
49
+ #
50
+ # Set cwd, environment, appname, etc.
51
+ #
52
+ app_name, command_line = command_to_run
53
+ create_process_args = {
54
+ :app_name => app_name,
55
+ :command_line => command_line,
56
+ :startup_info => {
57
+ :stdout => stdout_write,
58
+ :stderr => stderr_write
59
+ },
60
+ :environment => inherit_environment.map { |k,v| "#{k}=#{v}" },
61
+ :close_handles => false
62
+ }
63
+ create_process_args[:cwd] = cwd if cwd
64
+
65
+ #
66
+ # Start the process
67
+ #
68
+ process = Process.create(create_process_args)
69
+ begin
70
+
71
+ #
72
+ # Wait for the process to finish, consuming output as we go
73
+ #
74
+ start_wait = Time.now
75
+ while true
76
+ wait_status = WaitForSingleObject(process.process_handle, 0)
77
+ case wait_status
78
+ when WAIT_OBJECT_0
79
+ # Get process exit code
80
+ exit_code = [0].pack('l')
81
+ unless GetExitCodeProcess(process.process_handle, exit_code)
82
+ raise get_last_error
83
+ end
84
+ @status = ThingThatLooksSortOfLikeAProcessStatus.new
85
+ @status.exitstatus = exit_code.unpack('l').first
86
+
87
+ return self
88
+ when WAIT_TIMEOUT
89
+ # Kill the process
90
+ if (Time.now - start_wait) > timeout
91
+ raise Chef::Exceptions::CommandTimeout, "command timed out:\n#{format_for_exception}"
92
+ end
93
+
94
+ consume_output(open_streams, stdout_read, stderr_read)
95
+ else
96
+ raise "Unknown response from WaitForSingleObject(#{process.process_handle}, #{timeout*1000}): #{wait_status}"
56
97
  end
98
+
57
99
  end
58
100
 
59
- out_reader.join
60
- err_reader.join
101
+ ensure
102
+ CloseHandle(process.thread_handle)
103
+ CloseHandle(process.process_handle)
104
+ end
105
+
106
+ ensure
107
+ #
108
+ # Consume all remaining data from the pipes until they are closed
109
+ #
110
+ stdout_write.close
111
+ stderr_write.close
112
+
113
+ while consume_output(open_streams, stdout_read, stderr_read)
61
114
  end
62
115
  end
116
+ end
63
117
 
64
- @status = $?
118
+ private
119
+
120
+ class ThingThatLooksSortOfLikeAProcessStatus
121
+ attr_accessor :exitstatus
122
+ end
65
123
 
66
- self
124
+ def consume_output(open_streams, stdout_read, stderr_read)
125
+ return false if open_streams.length == 0
126
+ ready = IO.select(open_streams, nil, nil, READ_WAIT_TIME)
127
+ return true if ! ready
67
128
 
68
- rescue Timeout::Error
69
- raise Chef::Exceptions::CommandTimeout, "command timed out:\n#{format_for_exception}"
129
+ if ready.first.include?(stdout_read)
130
+ begin
131
+ next_chunk = stdout_read.readpartial(READ_SIZE)
132
+ @stdout << next_chunk
133
+ @live_stream << next_chunk if @live_stream
134
+ rescue EOFError
135
+ stdout_read.close
136
+ open_streams.delete(stdout_read)
137
+ end
138
+ end
139
+
140
+ if ready.first.include?(stderr_read)
141
+ begin
142
+ @stderr << stderr_read.readpartial(READ_SIZE)
143
+ rescue EOFError
144
+ stderr_read.close
145
+ open_streams.delete(stderr_read)
146
+ end
147
+ end
148
+
149
+ return true
70
150
  end
71
151
 
72
- def read_stdout(stdout)
73
- return nil if @finished_stdout
74
- if chunk = stdout.sysread(8096)
75
- @stdout << chunk
152
+ SHOULD_USE_CMD = /['"<>|&%]|\b(?:assoc|break|call|cd|chcp|chdir|cls|color|copy|ctty|date|del|dir|echo|endlocal|erase|exit|for|ftype|goto|if|lfnfor|lh|lock|md|mkdir|move|path|pause|popd|prompt|pushd|rd|rem|ren|rename|rmdir|set|setlocal|shift|start|time|title|truename|type|unlock|ver|verify|vol)\b/
153
+
154
+ def command_to_run
155
+ if command =~ SHOULD_USE_CMD
156
+ [ ENV['COMSPEC'], "cmd /c #{command}" ]
76
157
  else
77
- @finished_stdout = true
158
+ [ which(command[0,command.index(/\s/) || command.length]), command ]
159
+ end
160
+ end
161
+
162
+ def inherit_environment
163
+ result = {}
164
+ ENV.each_pair do |k,v|
165
+ result[k] = v
166
+ end
167
+
168
+ environment.each_pair do |k,v|
169
+ if v == nil
170
+ result.delete(k)
171
+ else
172
+ result[k] = v
173
+ end
174
+ end
175
+ result
176
+ end
177
+
178
+ def which(cmd)
179
+ return cmd if File.executable? cmd
180
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') + [''] : ['']
181
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
182
+ exts.each { |ext|
183
+ exe = "#{path}/#{cmd}#{ext}"
184
+ return exe if File.executable? exe
185
+ }
186
+ end
187
+ return nil
188
+ end
189
+ end # class
190
+ end
191
+ end
192
+
193
+ #
194
+ # Override module Windows::Process.CreateProcess to fix bug when
195
+ # using both app_name and command_line
196
+ #
197
+ module Windows
198
+ module Process
199
+ API.new('CreateProcess', 'SPPPLLLPPP', 'B')
200
+ end
201
+ end
202
+
203
+ #
204
+ # Override Win32::Process.create to take a proper environment hash
205
+ # so that variables can contain semicolons
206
+ # (submitted patch to owner)
207
+ #
208
+ module Process
209
+ def create(args)
210
+ unless args.kind_of?(Hash)
211
+ raise TypeError, 'Expecting hash-style keyword arguments'
212
+ end
213
+
214
+ valid_keys = %w/
215
+ app_name command_line inherit creation_flags cwd environment
216
+ startup_info thread_inherit process_inherit close_handles with_logon
217
+ domain password
218
+ /
219
+
220
+ valid_si_keys = %/
221
+ startf_flags desktop title x y x_size y_size x_count_chars
222
+ y_count_chars fill_attribute sw_flags stdin stdout stderr
223
+ /
224
+
225
+ # Set default values
226
+ hash = {
227
+ 'app_name' => nil,
228
+ 'creation_flags' => 0,
229
+ 'close_handles' => true
230
+ }
231
+
232
+ # Validate the keys, and convert symbols and case to lowercase strings.
233
+ args.each{ |key, val|
234
+ key = key.to_s.downcase
235
+ unless valid_keys.include?(key)
236
+ raise ArgumentError, "invalid key '#{key}'"
237
+ end
238
+ hash[key] = val
239
+ }
240
+
241
+ si_hash = {}
242
+
243
+ # If the startup_info key is present, validate its subkeys
244
+ if hash['startup_info']
245
+ hash['startup_info'].each{ |key, val|
246
+ key = key.to_s.downcase
247
+ unless valid_si_keys.include?(key)
248
+ raise ArgumentError, "invalid startup_info key '#{key}'"
78
249
  end
79
- rescue EOFError
80
- @finished_stdout = true
81
- rescue Errno::EAGAIN
250
+ si_hash[key] = val
251
+ }
252
+ end
253
+
254
+ # The +command_line+ key is mandatory unless the +app_name+ key
255
+ # is specified.
256
+ unless hash['command_line']
257
+ if hash['app_name']
258
+ hash['command_line'] = hash['app_name']
259
+ hash['app_name'] = nil
260
+ else
261
+ raise ArgumentError, 'command_line or app_name must be specified'
262
+ end
263
+ end
264
+
265
+ # The environment string should be passed as an array of A=B paths, or
266
+ # as a string of ';' separated paths.
267
+ if hash['environment']
268
+ env = hash['environment']
269
+ if !env.respond_to?(:join)
270
+ # Backwards compat for ; separated paths
271
+ env = hash['environment'].split(File::PATH_SEPARATOR)
272
+ end
273
+ # The argument format is a series of null-terminated strings, with an additional null terminator.
274
+ env = env.map { |e| e + "\0" }.join("") + "\0"
275
+ if hash['with_logon']
276
+ env = env.multi_to_wide(e)
82
277
  end
278
+ env = [env].pack('p*').unpack('L').first
279
+ else
280
+ env = nil
281
+ end
282
+
283
+ startinfo = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
284
+ startinfo = startinfo.pack('LLLLLLLLLLLLSSLLLL')
285
+ procinfo = [0,0,0,0].pack('LLLL')
83
286
 
84
- def read_stderr(stderr)
85
- return nil if @finished_stderr
86
- if chunk = stderr.sysread(8096)
87
- @stderr << chunk
287
+ # Process SECURITY_ATTRIBUTE structure
288
+ process_security = 0
289
+ if hash['process_inherit']
290
+ process_security = [0,0,0].pack('LLL')
291
+ process_security[0,4] = [12].pack('L') # sizeof(SECURITY_ATTRIBUTE)
292
+ process_security[8,4] = [1].pack('L') # TRUE
293
+ end
294
+
295
+ # Thread SECURITY_ATTRIBUTE structure
296
+ thread_security = 0
297
+ if hash['thread_inherit']
298
+ thread_security = [0,0,0].pack('LLL')
299
+ thread_security[0,4] = [12].pack('L') # sizeof(SECURITY_ATTRIBUTE)
300
+ thread_security[8,4] = [1].pack('L') # TRUE
301
+ end
302
+
303
+ # Automatically handle stdin, stdout and stderr as either IO objects
304
+ # or file descriptors. This won't work for StringIO, however.
305
+ ['stdin', 'stdout', 'stderr'].each{ |io|
306
+ if si_hash[io]
307
+ if si_hash[io].respond_to?(:fileno)
308
+ handle = get_osfhandle(si_hash[io].fileno)
88
309
  else
89
- @finished_stderr = true
310
+ handle = get_osfhandle(si_hash[io])
311
+ end
312
+
313
+ if handle == INVALID_HANDLE_VALUE
314
+ raise Error, get_last_error
90
315
  end
91
- rescue EOFError
92
- @finished_stderr = true
93
- rescue Errno::EAGAIN
316
+
317
+ # Most implementations of Ruby on Windows create inheritable
318
+ # handles by default, but some do not. RF bug #26988.
319
+ bool = SetHandleInformation(
320
+ handle,
321
+ HANDLE_FLAG_INHERIT,
322
+ HANDLE_FLAG_INHERIT
323
+ )
324
+
325
+ raise Error, get_last_error unless bool
326
+
327
+ si_hash[io] = handle
328
+ si_hash['startf_flags'] ||= 0
329
+ si_hash['startf_flags'] |= STARTF_USESTDHANDLES
330
+ hash['inherit'] = true
94
331
  end
332
+ }
333
+
334
+ # The bytes not covered here are reserved (null)
335
+ unless si_hash.empty?
336
+ startinfo[0,4] = [startinfo.size].pack('L')
337
+ startinfo[8,4] = [si_hash['desktop']].pack('p*') if si_hash['desktop']
338
+ startinfo[12,4] = [si_hash['title']].pack('p*') if si_hash['title']
339
+ startinfo[16,4] = [si_hash['x']].pack('L') if si_hash['x']
340
+ startinfo[20,4] = [si_hash['y']].pack('L') if si_hash['y']
341
+ startinfo[24,4] = [si_hash['x_size']].pack('L') if si_hash['x_size']
342
+ startinfo[28,4] = [si_hash['y_size']].pack('L') if si_hash['y_size']
343
+ startinfo[32,4] = [si_hash['x_count_chars']].pack('L') if si_hash['x_count_chars']
344
+ startinfo[36,4] = [si_hash['y_count_chars']].pack('L') if si_hash['y_count_chars']
345
+ startinfo[40,4] = [si_hash['fill_attribute']].pack('L') if si_hash['fill_attribute']
346
+ startinfo[44,4] = [si_hash['startf_flags']].pack('L') if si_hash['startf_flags']
347
+ startinfo[48,2] = [si_hash['sw_flags']].pack('S') if si_hash['sw_flags']
348
+ startinfo[56,4] = [si_hash['stdin']].pack('L') if si_hash['stdin']
349
+ startinfo[60,4] = [si_hash['stdout']].pack('L') if si_hash['stdout']
350
+ startinfo[64,4] = [si_hash['stderr']].pack('L') if si_hash['stderr']
351
+ end
352
+
353
+ if hash['with_logon']
354
+ logon = multi_to_wide(hash['with_logon'])
355
+ domain = multi_to_wide(hash['domain'])
356
+ app = hash['app_name'].nil? ? nil : multi_to_wide(hash['app_name'])
357
+ cmd = hash['command_line'].nil? ? nil : multi_to_wide(hash['command_line'])
358
+ cwd = multi_to_wide(hash['cwd'])
359
+ passwd = multi_to_wide(hash['password'])
360
+
361
+ hash['creation_flags'] |= CREATE_UNICODE_ENVIRONMENT
362
+
363
+ process_ran = CreateProcessWithLogonW(
364
+ logon, # User
365
+ domain, # Domain
366
+ passwd, # Password
367
+ LOGON_WITH_PROFILE, # Logon flags
368
+ app, # App name
369
+ cmd, # Command line
370
+ hash['creation_flags'], # Creation flags
371
+ env, # Environment
372
+ cwd, # Working directory
373
+ startinfo, # Startup Info
374
+ procinfo # Process Info
375
+ )
376
+ else
377
+ process_ran = CreateProcess(
378
+ hash['app_name'], # App name
379
+ hash['command_line'], # Command line
380
+ process_security, # Process attributes
381
+ thread_security, # Thread attributes
382
+ hash['inherit'], # Inherit handles?
383
+ hash['creation_flags'], # Creation flags
384
+ env, # Environment
385
+ hash['cwd'], # Working directory
386
+ startinfo, # Startup Info
387
+ procinfo # Process Info
388
+ )
389
+ end
95
390
 
391
+ # TODO: Close stdin, stdout and stderr handles in the si_hash unless
392
+ # they're pointing to one of the standard handles already. [Maybe]
393
+ if !process_ran
394
+ raise_last_error("CreateProcess()")
96
395
  end
396
+
397
+ # Automatically close the process and thread handles in the
398
+ # PROCESS_INFORMATION struct unless explicitly told not to.
399
+ if hash['close_handles']
400
+ CloseHandle(procinfo[0,4].unpack('L').first)
401
+ CloseHandle(procinfo[4,4].unpack('L').first)
402
+ end
403
+
404
+ ProcessInfo.new(
405
+ procinfo[0,4].unpack('L').first, # hProcess
406
+ procinfo[4,4].unpack('L').first, # hThread
407
+ procinfo[8,4].unpack('L').first, # hProcessId
408
+ procinfo[12,4].unpack('L').first # hThreadId
409
+ )
97
410
  end
98
- end
411
+
412
+ def self.raise_last_error(operation)
413
+ error_string = "#{operation} failed: #{get_last_error}"
414
+ last_error_code = GetLastError()
415
+ if ERROR_CODE_MAP.has_key?(last_error_code)
416
+ raise ERROR_CODE_MAP[last_error_code], error_string
417
+ else
418
+ raise Error, error_string
419
+ end
420
+ end
421
+
422
+ # List from ruby/win32/win32.c
423
+ ERROR_CODE_MAP = {
424
+ ERROR_INVALID_FUNCTION => Errno::EINVAL,
425
+ ERROR_FILE_NOT_FOUND => Errno::ENOENT,
426
+ ERROR_PATH_NOT_FOUND => Errno::ENOENT,
427
+ ERROR_TOO_MANY_OPEN_FILES => Errno::EMFILE,
428
+ ERROR_ACCESS_DENIED => Errno::EACCES,
429
+ ERROR_INVALID_HANDLE => Errno::EBADF,
430
+ ERROR_ARENA_TRASHED => Errno::ENOMEM,
431
+ ERROR_NOT_ENOUGH_MEMORY => Errno::ENOMEM,
432
+ ERROR_INVALID_BLOCK => Errno::ENOMEM,
433
+ ERROR_BAD_ENVIRONMENT => Errno::E2BIG,
434
+ ERROR_BAD_FORMAT => Errno::ENOEXEC,
435
+ ERROR_INVALID_ACCESS => Errno::EINVAL,
436
+ ERROR_INVALID_DATA => Errno::EINVAL,
437
+ ERROR_INVALID_DRIVE => Errno::ENOENT,
438
+ ERROR_CURRENT_DIRECTORY => Errno::EACCES,
439
+ ERROR_NOT_SAME_DEVICE => Errno::EXDEV,
440
+ ERROR_NO_MORE_FILES => Errno::ENOENT,
441
+ ERROR_WRITE_PROTECT => Errno::EROFS,
442
+ ERROR_BAD_UNIT => Errno::ENODEV,
443
+ ERROR_NOT_READY => Errno::ENXIO,
444
+ ERROR_BAD_COMMAND => Errno::EACCES,
445
+ ERROR_CRC => Errno::EACCES,
446
+ ERROR_BAD_LENGTH => Errno::EACCES,
447
+ ERROR_SEEK => Errno::EIO,
448
+ ERROR_NOT_DOS_DISK => Errno::EACCES,
449
+ ERROR_SECTOR_NOT_FOUND => Errno::EACCES,
450
+ ERROR_OUT_OF_PAPER => Errno::EACCES,
451
+ ERROR_WRITE_FAULT => Errno::EIO,
452
+ ERROR_READ_FAULT => Errno::EIO,
453
+ ERROR_GEN_FAILURE => Errno::EACCES,
454
+ ERROR_LOCK_VIOLATION => Errno::EACCES,
455
+ ERROR_SHARING_VIOLATION => Errno::EACCES,
456
+ ERROR_WRONG_DISK => Errno::EACCES,
457
+ ERROR_SHARING_BUFFER_EXCEEDED => Errno::EACCES,
458
+ # ERROR_BAD_NETPATH => Errno::ENOENT,
459
+ # ERROR_NETWORK_ACCESS_DENIED => Errno::EACCES,
460
+ # ERROR_BAD_NET_NAME => Errno::ENOENT,
461
+ ERROR_FILE_EXISTS => Errno::EEXIST,
462
+ ERROR_CANNOT_MAKE => Errno::EACCES,
463
+ ERROR_FAIL_I24 => Errno::EACCES,
464
+ ERROR_INVALID_PARAMETER => Errno::EINVAL,
465
+ ERROR_NO_PROC_SLOTS => Errno::EAGAIN,
466
+ ERROR_DRIVE_LOCKED => Errno::EACCES,
467
+ ERROR_BROKEN_PIPE => Errno::EPIPE,
468
+ ERROR_DISK_FULL => Errno::ENOSPC,
469
+ ERROR_INVALID_TARGET_HANDLE => Errno::EBADF,
470
+ ERROR_INVALID_HANDLE => Errno::EINVAL,
471
+ ERROR_WAIT_NO_CHILDREN => Errno::ECHILD,
472
+ ERROR_CHILD_NOT_COMPLETE => Errno::ECHILD,
473
+ ERROR_DIRECT_ACCESS_HANDLE => Errno::EBADF,
474
+ ERROR_NEGATIVE_SEEK => Errno::EINVAL,
475
+ ERROR_SEEK_ON_DEVICE => Errno::EACCES,
476
+ ERROR_DIR_NOT_EMPTY => Errno::ENOTEMPTY,
477
+ # ERROR_DIRECTORY => Errno::ENOTDIR,
478
+ ERROR_NOT_LOCKED => Errno::EACCES,
479
+ ERROR_BAD_PATHNAME => Errno::ENOENT,
480
+ ERROR_MAX_THRDS_REACHED => Errno::EAGAIN,
481
+ # ERROR_LOCK_FAILED => Errno::EACCES,
482
+ ERROR_ALREADY_EXISTS => Errno::EEXIST,
483
+ ERROR_INVALID_STARTING_CODESEG => Errno::ENOEXEC,
484
+ ERROR_INVALID_STACKSEG => Errno::ENOEXEC,
485
+ ERROR_INVALID_MODULETYPE => Errno::ENOEXEC,
486
+ ERROR_INVALID_EXE_SIGNATURE => Errno::ENOEXEC,
487
+ ERROR_EXE_MARKED_INVALID => Errno::ENOEXEC,
488
+ ERROR_BAD_EXE_FORMAT => Errno::ENOEXEC,
489
+ ERROR_ITERATED_DATA_EXCEEDS_64k => Errno::ENOEXEC,
490
+ ERROR_INVALID_MINALLOCSIZE => Errno::ENOEXEC,
491
+ ERROR_DYNLINK_FROM_INVALID_RING => Errno::ENOEXEC,
492
+ ERROR_IOPL_NOT_ENABLED => Errno::ENOEXEC,
493
+ ERROR_INVALID_SEGDPL => Errno::ENOEXEC,
494
+ ERROR_AUTODATASEG_EXCEEDS_64k => Errno::ENOEXEC,
495
+ ERROR_RING2SEG_MUST_BE_MOVABLE => Errno::ENOEXEC,
496
+ ERROR_RELOC_CHAIN_XEEDS_SEGLIM => Errno::ENOEXEC,
497
+ ERROR_INFLOOP_IN_RELOC_CHAIN => Errno::ENOEXEC,
498
+ ERROR_FILENAME_EXCED_RANGE => Errno::ENOENT,
499
+ ERROR_NESTING_NOT_ALLOWED => Errno::EAGAIN,
500
+ # ERROR_PIPE_LOCAL => Errno::EPIPE,
501
+ ERROR_BAD_PIPE => Errno::EPIPE,
502
+ ERROR_PIPE_BUSY => Errno::EAGAIN,
503
+ ERROR_NO_DATA => Errno::EPIPE,
504
+ ERROR_PIPE_NOT_CONNECTED => Errno::EPIPE,
505
+ ERROR_OPERATION_ABORTED => Errno::EINTR,
506
+ # ERROR_NOT_ENOUGH_QUOTA => Errno::ENOMEM,
507
+ ERROR_MOD_NOT_FOUND => Errno::ENOENT,
508
+ WSAEINTR => Errno::EINTR,
509
+ WSAEBADF => Errno::EBADF,
510
+ # WSAEACCES => Errno::EACCES,
511
+ WSAEFAULT => Errno::EFAULT,
512
+ WSAEINVAL => Errno::EINVAL,
513
+ WSAEMFILE => Errno::EMFILE,
514
+ WSAEWOULDBLOCK => Errno::EWOULDBLOCK,
515
+ WSAEINPROGRESS => Errno::EINPROGRESS,
516
+ WSAEALREADY => Errno::EALREADY,
517
+ WSAENOTSOCK => Errno::ENOTSOCK,
518
+ WSAEDESTADDRREQ => Errno::EDESTADDRREQ,
519
+ WSAEMSGSIZE => Errno::EMSGSIZE,
520
+ WSAEPROTOTYPE => Errno::EPROTOTYPE,
521
+ WSAENOPROTOOPT => Errno::ENOPROTOOPT,
522
+ WSAEPROTONOSUPPORT => Errno::EPROTONOSUPPORT,
523
+ WSAESOCKTNOSUPPORT => Errno::ESOCKTNOSUPPORT,
524
+ WSAEOPNOTSUPP => Errno::EOPNOTSUPP,
525
+ WSAEPFNOSUPPORT => Errno::EPFNOSUPPORT,
526
+ WSAEAFNOSUPPORT => Errno::EAFNOSUPPORT,
527
+ WSAEADDRINUSE => Errno::EADDRINUSE,
528
+ WSAEADDRNOTAVAIL => Errno::EADDRNOTAVAIL,
529
+ WSAENETDOWN => Errno::ENETDOWN,
530
+ WSAENETUNREACH => Errno::ENETUNREACH,
531
+ WSAENETRESET => Errno::ENETRESET,
532
+ WSAECONNABORTED => Errno::ECONNABORTED,
533
+ WSAECONNRESET => Errno::ECONNRESET,
534
+ WSAENOBUFS => Errno::ENOBUFS,
535
+ WSAEISCONN => Errno::EISCONN,
536
+ WSAENOTCONN => Errno::ENOTCONN,
537
+ WSAESHUTDOWN => Errno::ESHUTDOWN,
538
+ WSAETOOMANYREFS => Errno::ETOOMANYREFS,
539
+ # WSAETIMEDOUT => Errno::ETIMEDOUT,
540
+ WSAECONNREFUSED => Errno::ECONNREFUSED,
541
+ WSAELOOP => Errno::ELOOP,
542
+ WSAENAMETOOLONG => Errno::ENAMETOOLONG,
543
+ WSAEHOSTDOWN => Errno::EHOSTDOWN,
544
+ WSAEHOSTUNREACH => Errno::EHOSTUNREACH,
545
+ # WSAEPROCLIM => Errno::EPROCLIM,
546
+ # WSAENOTEMPTY => Errno::ENOTEMPTY,
547
+ WSAEUSERS => Errno::EUSERS,
548
+ WSAEDQUOT => Errno::EDQUOT,
549
+ WSAESTALE => Errno::ESTALE,
550
+ WSAEREMOTE => Errno::EREMOTE
551
+ }
552
+
553
+ module_function :create
554
+ end