right_popen 1.0.7-x86-mswin32-60 → 1.0.9-x86-mswin32-60

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE CHANGED
@@ -1,20 +1,20 @@
1
- Copyright (c) 2010 RightScale, Inc.
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining
4
- a copy of this software and associated documentation files (the
5
- 'Software'), to deal in the Software without restriction, including
6
- without limitation the rights to use, copy, modify, merge, publish,
7
- distribute, sublicense, and/or sell copies of the Software, and to
8
- permit persons to whom the Software is furnished to do so, subject to
9
- the following conditions:
10
-
11
- The above copyright notice and this permission notice shall be
12
- included in all copies or substantial portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
15
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1
+ Copyright (c) 2010 RightScale, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ 'Software'), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc CHANGED
@@ -1,124 +1,124 @@
1
- = RightPopen
2
-
3
- == DESCRIPTION
4
-
5
- === Synopsis
6
-
7
- RightPopen allows running external processes aynchronously while still
8
- capturing their standard and error outputs. It relies on EventMachine for most
9
- of its internal mechanisms. The Linux implementation is valid for any Linux
10
- platform but there is also a native implementation for Windows platforms.
11
-
12
- Refer to the wiki (https://github.com/rightscale/right_popen/wikis) for up-to-date
13
- documentation.
14
-
15
- Also use the built-in issues tracker (https://github.com/rightscale/right_popen/issues)
16
- to report issues.
17
-
18
-
19
- == USAGE
20
-
21
- === Simple Example
22
-
23
- require 'rubygems'
24
- require 'right_popen'
25
-
26
- @stdout_text = ""
27
- @stderr_text = ""
28
- @exit_status = nil
29
-
30
- def on_read_stdout(data)
31
- @stdout_text << data
32
- end
33
-
34
- def on_read_stderr(data)
35
- @stderr_text << data
36
- end
37
-
38
- def on_exit(status)
39
- @exit_status = status
40
- end
41
-
42
- EM.run do
43
- EM.next_tick do
44
- command = "ruby -e \"puts 'some stdout text'; $stderr.puts 'some stderr text'\; exit 99\""
45
- RightScale.popen3(:command => command,
46
- :target => self,
47
- :environment => nil,
48
- :stdout_handler => :on_read_stdout,
49
- :stderr_handler => :on_read_stderr,
50
- :exit_handler => :on_exit)
51
- end
52
- timer = EM::PeriodicTimer.new(0.1) do
53
- if @exit_status
54
- timer.cancel
55
- EM.stop
56
- end
57
- end
58
- end
59
-
60
- puts "@stdout_text = #{@stdout_text}"
61
- puts "@stderr_text = #{@stderr_text}"
62
- puts "@exit_status.exitstatus = #{@exit_status.exitstatus}"
63
-
64
-
65
- == INSTALLATION
66
-
67
- RightPopen can be installed by entering the following at the command prompt:
68
-
69
- gem install right_popen
70
-
71
-
72
- == BUILDING
73
-
74
- Install the following RubyGems required for building:
75
- * rake
76
-
77
- The Windows implementation relies on a native C module which must currently be
78
- built using the MSVC 6.0 compiler due to a dependency on the standard libraries
79
- FILE struct provided by the "msvcrt.dll".
80
-
81
- The gem can be built on Linux or Windows platforms and will produce separate gem
82
- files depending on current platform. Run the following command from the
83
- directory containing the "Rakefile":
84
-
85
- rake build_binary_gem
86
-
87
-
88
- == TESTING
89
-
90
- Install the following RubyGems required for testing:
91
- * rspec
92
-
93
- The build can be tested using the RSpec gem. Create a link to the installed
94
- "spec" in your Ruby/bin directory (or ensure the bin directory is on the PATH
95
- under Windows) and run the following command from the gem directory to execute
96
- the RightPopen tests:
97
-
98
- rake spec
99
-
100
-
101
- == LICENSE
102
-
103
- <b>RightPopen</b>
104
-
105
- Copyright:: Copyright (c) 2010 RightScale, Inc.
106
-
107
- Permission is hereby granted, free of charge, to any person obtaining
108
- a copy of this software and associated documentation files (the
109
- 'Software'), to deal in the Software without restriction, including
110
- without limitation the rights to use, copy, modify, merge, publish,
111
- distribute, sublicense, and/or sell copies of the Software, and to
112
- permit persons to whom the Software is furnished to do so, subject to
113
- the following conditions:
114
-
115
- The above copyright notice and this permission notice shall be
116
- included in all copies or substantial portions of the Software.
117
-
118
- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
119
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
120
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
121
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
122
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
123
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
124
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1
+ = RightPopen
2
+
3
+ == DESCRIPTION
4
+
5
+ === Synopsis
6
+
7
+ RightPopen allows running external processes aynchronously while still
8
+ capturing their standard and error outputs. It relies on EventMachine for most
9
+ of its internal mechanisms. The Linux implementation is valid for any Linux
10
+ platform but there is also a native implementation for Windows platforms.
11
+
12
+ Refer to the wiki (https://github.com/rightscale/right_popen/wikis) for up-to-date
13
+ documentation.
14
+
15
+ Also use the built-in issues tracker (https://github.com/rightscale/right_popen/issues)
16
+ to report issues.
17
+
18
+
19
+ == USAGE
20
+
21
+ === Simple Example
22
+
23
+ require 'rubygems'
24
+ require 'right_popen'
25
+
26
+ @stdout_text = ""
27
+ @stderr_text = ""
28
+ @exit_status = nil
29
+
30
+ def on_read_stdout(data)
31
+ @stdout_text << data
32
+ end
33
+
34
+ def on_read_stderr(data)
35
+ @stderr_text << data
36
+ end
37
+
38
+ def on_exit(status)
39
+ @exit_status = status
40
+ end
41
+
42
+ EM.run do
43
+ EM.next_tick do
44
+ command = "ruby -e \"puts 'some stdout text'; $stderr.puts 'some stderr text'\; exit 99\""
45
+ RightScale.popen3(:command => command,
46
+ :target => self,
47
+ :environment => nil,
48
+ :stdout_handler => :on_read_stdout,
49
+ :stderr_handler => :on_read_stderr,
50
+ :exit_handler => :on_exit)
51
+ end
52
+ timer = EM::PeriodicTimer.new(0.1) do
53
+ if @exit_status
54
+ timer.cancel
55
+ EM.stop
56
+ end
57
+ end
58
+ end
59
+
60
+ puts "@stdout_text = #{@stdout_text}"
61
+ puts "@stderr_text = #{@stderr_text}"
62
+ puts "@exit_status.exitstatus = #{@exit_status.exitstatus}"
63
+
64
+
65
+ == INSTALLATION
66
+
67
+ RightPopen can be installed by entering the following at the command prompt:
68
+
69
+ gem install right_popen
70
+
71
+
72
+ == BUILDING
73
+
74
+ Install the following RubyGems required for building:
75
+ * rake
76
+
77
+ The Windows implementation relies on a native C module which must currently be
78
+ built using the MSVC 6.0 compiler due to a dependency on the standard libraries
79
+ FILE struct provided by the "msvcrt.dll".
80
+
81
+ The gem can be built on Linux or Windows platforms and will produce separate gem
82
+ files depending on current platform. Run the following command from the
83
+ directory containing the "Rakefile":
84
+
85
+ rake build_binary_gem
86
+
87
+
88
+ == TESTING
89
+
90
+ Install the following RubyGems required for testing:
91
+ * rspec
92
+
93
+ The build can be tested using the RSpec gem. Create a link to the installed
94
+ "spec" in your Ruby/bin directory (or ensure the bin directory is on the PATH
95
+ under Windows) and run the following command from the gem directory to execute
96
+ the RightPopen tests:
97
+
98
+ rake spec
99
+
100
+
101
+ == LICENSE
102
+
103
+ <b>RightPopen</b>
104
+
105
+ Copyright:: Copyright (c) 2010 RightScale, Inc.
106
+
107
+ Permission is hereby granted, free of charge, to any person obtaining
108
+ a copy of this software and associated documentation files (the
109
+ 'Software'), to deal in the Software without restriction, including
110
+ without limitation the rights to use, copy, modify, merge, publish,
111
+ distribute, sublicense, and/or sell copies of the Software, and to
112
+ permit persons to whom the Software is furnished to do so, subject to
113
+ the following conditions:
114
+
115
+ The above copyright notice and this permission notice shall be
116
+ included in all copies or substantial portions of the Software.
117
+
118
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
119
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
120
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
121
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
122
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
123
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
124
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/lib/right_popen.rb CHANGED
@@ -1,65 +1,66 @@
1
- #--
2
- # Copyright (c) 2009 RightScale Inc
3
- #
4
- # Permission is hereby granted, free of charge, to any person obtaining
5
- # a copy of this software and associated documentation files (the
6
- # "Software"), to deal in the Software without restriction, including
7
- # without limitation the rights to use, copy, modify, merge, publish,
8
- # distribute, sublicense, and/or sell copies of the Software, and to
9
- # permit persons to whom the Software is furnished to do so, subject to
10
- # the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be
13
- # included in all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
- #++
23
-
24
- # RightScale.popen3 allows running external processes aynchronously
25
- # while still capturing their standard and error outputs.
26
- # It relies on EventMachine for most of its internal mechanisms.
27
-
28
- if RUBY_PLATFORM =~ /mswin/
29
- require File.expand_path(File.join(File.dirname(__FILE__), 'win32', 'right_popen'))
30
- else
31
- require File.expand_path(File.join(File.dirname(__FILE__), 'linux', 'right_popen'))
32
- end
33
-
34
- module RightScale
35
-
36
- # Spawn process to run given command asynchronously, hooking all three
37
- # standard streams of the child process.
38
- #
39
- # Streams the command's stdout and stderr to the given handlers. Time-
40
- # ordering of bytes sent to stdout and stderr is not preserved.
41
- #
42
- # Calls given exit handler upon command process termination, passing in the
43
- # resulting Process::Status.
44
- #
45
- # All handlers must be methods exposed by the given target.
46
- #
47
- # === Parameters
48
- # options[:command](String):: Command to execute, including any arguments
49
- # options[:environment](Hash):: Hash of environment variables values keyed by name
50
- # options[:target](Object):: object defining handler methods to be called, optional (no handlers can be defined if not specified)
51
- # options[:stdout_handler](String):: Stdout handler method name, optional
52
- # options[:stderr_handler](String):: Stderr handler method name, optional
53
- # options[:exit_handler](String):: Exit handler method name, optional
54
- #
55
- # === Returns
56
- # true:: Always returns true
57
- def self.popen3(options)
58
- raise "EventMachine reactor must be started" unless EM.reactor_running?
59
- raise "Missing command" unless options[:command]
60
- raise "Missing target" unless options[:target] || !options[:stdout_handler] && !options[:stderr_handler] && !options[:exit_handler]
61
- RightScale.popen3_imp(options)
62
- true
63
- end
64
-
65
- end
1
+ #--
2
+ # Copyright (c) 2009 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ # RightScale.popen3 allows running external processes aynchronously
25
+ # while still capturing their standard and error outputs.
26
+ # It relies on EventMachine for most of its internal mechanisms.
27
+
28
+ if RUBY_PLATFORM =~ /mswin/
29
+ require File.expand_path(File.join(File.dirname(__FILE__), 'win32', 'right_popen'))
30
+ else
31
+ require File.expand_path(File.join(File.dirname(__FILE__), 'linux', 'right_popen'))
32
+ end
33
+
34
+ module RightScale
35
+
36
+ # Spawn process to run given command asynchronously, hooking all three
37
+ # standard streams of the child process.
38
+ #
39
+ # Streams the command's stdout and stderr to the given handlers. Time-
40
+ # ordering of bytes sent to stdout and stderr is not preserved.
41
+ #
42
+ # Calls given exit handler upon command process termination, passing in the
43
+ # resulting Process::Status.
44
+ #
45
+ # All handlers must be methods exposed by the given target.
46
+ #
47
+ # === Parameters
48
+ # options[:command](String):: Command to execute, including any arguments
49
+ # options[:environment](Hash):: Hash of environment variables values keyed by name
50
+ # options[:input](String):: Input string that will get streamed into child's process stdin
51
+ # options[:target](Object):: object defining handler methods to be called, optional (no handlers can be defined if not specified)
52
+ # options[:stdout_handler](String):: Stdout handler method name, optional
53
+ # options[:stderr_handler](String):: Stderr handler method name, optional
54
+ # options[:exit_handler](String):: Exit handler method name, optional
55
+ #
56
+ # === Returns
57
+ # true:: Always returns true
58
+ def self.popen3(options)
59
+ raise "EventMachine reactor must be started" unless EM.reactor_running?
60
+ raise "Missing command" unless options[:command]
61
+ raise "Missing target" unless options[:target] || !options[:stdout_handler] && !options[:stderr_handler] && !options[:exit_handler]
62
+ RightScale.popen3_imp(options)
63
+ true
64
+ end
65
+
66
+ end
@@ -1,442 +1,461 @@
1
- #--
2
- # Copyright (c) 2009 RightScale Inc
3
- #
4
- # Permission is hereby granted, free of charge, to any person obtaining
5
- # a copy of this software and associated documentation files (the
6
- # "Software"), to deal in the Software without restriction, including
7
- # without limitation the rights to use, copy, modify, merge, publish,
8
- # distribute, sublicense, and/or sell copies of the Software, and to
9
- # permit persons to whom the Software is furnished to do so, subject to
10
- # the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be
13
- # included in all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
- #++
23
-
24
- require 'rubygems'
25
- begin
26
- gem 'eventmachine', '=0.12.10.1' # patched version for Windows-only socket close fix
27
- rescue Gem::LoadError
28
- gem 'eventmachine', '=0.12.10'
29
- end
30
- require 'eventmachine'
31
- require 'win32/process'
32
-
33
- require File.join(File.dirname(__FILE__), 'right_popen.so') # win32 native code
34
-
35
- module RightScale
36
-
37
- # Provides an eventmachine callback handler for the stdout stream.
38
- module StdOutHandler
39
-
40
- # Quacks like Process::Status, which we cannot instantiate ourselves because
41
- # has no public new method. RightScale::popen3 needs this because the
42
- # 'win32/process' gem currently won't return Process::Status objects but
43
- # only returns a [pid, exitstatus] value.
44
- class Status
45
- # Process ID
46
- attr_reader :pid
47
-
48
- # Process exit code
49
- attr_reader :exitstatus
50
-
51
- # === Parameters
52
- # pid(Integer):: Process ID.
53
- #
54
- # exitstatus(Integer):: Process exit code
55
- def initialize(pid, exitstatus)
56
- @pid = pid
57
- @exitstatus = exitstatus
58
- end
59
-
60
- # Simulates Process::Status.exited?
61
- #
62
- # === Returns
63
- # true in all cases because this object cannot be returned until the
64
- # process exits
65
- def exited?
66
- return true
67
- end
68
-
69
- # Simulates Process::Status.success?
70
- #
71
- # === Returns
72
- # true if the process returned zero as its exit code
73
- def success?
74
- return @exitstatus ? (0 == @exitstatus) : true;
75
- end
76
- end
77
-
78
- # === Parameters
79
- # target(Object):: Object defining handler methods to be called.
80
- # stdout_handler(String):: Token for stdout handler method name.
81
- # exit_handler(String):: Token for exit handler method name.
82
- # stderr_eventable(Connector):: EM object representing stderr handler.
83
- # stream_out(IO):: Standard output stream.
84
- # pid(Integer):: Child process ID.
85
- def initialize(target, stdout_handler, exit_handler, stderr_eventable, stream_out, pid)
86
- @target = target
87
- @stdout_handler = stdout_handler
88
- @exit_handler = exit_handler
89
- @stderr_eventable = stderr_eventable
90
- @stream_out = stream_out
91
- @pid = pid
92
- @status = nil
93
- end
94
-
95
- # Callback from EM to asynchronously read the stdout stream. Note that this
96
- # callback mechanism is deprecated after EM v0.12.8
97
- def notify_readable
98
- data = RightPopen.async_read(@stream_out)
99
- receive_data(data) if (data && data.length > 0)
100
- detach unless data
101
- end
102
-
103
- # Callback from EM to receive data, which we also use to handle the
104
- # asynchronous data we read ourselves.
105
- def receive_data(data)
106
- @target.method(@stdout_handler).call(data) if @stdout_handler
107
- end
108
-
109
- # Override of Connection.get_status() for Windows implementation.
110
- def get_status
111
- unless @status
112
- begin
113
- @status = Status.new(@pid, Process.waitpid2(@pid)[1])
114
- rescue Process::Error
115
- # process is gone, which means we have no recourse to retrieve the
116
- # actual exit code; let's be optimistic.
117
- @status = Status.new(@pid, 0)
118
- end
119
- end
120
- return @status
121
- end
122
-
123
- # Callback from EM to unbind.
124
- def unbind
125
- # We force the stderr watched handler to go away so that
126
- # we don't end up with a broken pipe
127
- @stderr_eventable.force_detach if @stderr_eventable
128
- @target.method(@exit_handler).call(get_status) if @exit_handler
129
- @stream_out.close
130
- end
131
- end
132
-
133
- # Provides an eventmachine callback handler for the stderr stream.
134
- module StdErrHandler
135
-
136
- # === Parameters
137
- # target(Object):: Object defining handler methods to be called.
138
- #
139
- # stderr_handler(String):: Token for stderr handler method name.
140
- #
141
- # stream_err(IO):: Standard error stream.
142
- def initialize(target, stderr_handler, stream_err)
143
- @target = target
144
- @stderr_handler = stderr_handler
145
- @stream_err = stream_err
146
- @unbound = false
147
- end
148
-
149
- # Callback from EM to asynchronously read the stderr stream. Note that this
150
- # callback mechanism is deprecated after EM v0.12.8
151
- def notify_readable
152
- data = RightPopen.async_read(@stream_err)
153
- receive_data(data) if (data && data.length > 0)
154
- detach unless data
155
- end
156
-
157
- # Callback from EM to receive data, which we also use to handle the
158
- # asynchronous data we read ourselves.
159
- def receive_data(data)
160
- @target.method(@stderr_handler).call(data)
161
- end
162
-
163
- # Callback from EM to unbind.
164
- def unbind
165
- @unbound = true
166
- @stream_err.close
167
- end
168
-
169
- # Forces detachment of the stderr handler on EM's next tick.
170
- def force_detach
171
- # Use next tick to prevent issue in EM where descriptors list
172
- # gets out-of-sync when calling detach in an unbind callback
173
- EM.next_tick { detach unless @unbound }
174
- end
175
- end
176
-
177
- # Creates a child process and connects event handlers to the standard output
178
- # and error streams used by the created process. Connectors use named pipes
179
- # and asynchronous I/O in the native Windows implementation.
180
- #
181
- # See RightScale.popen3
182
- def self.popen3_imp(options)
183
- raise "EventMachine reactor must be started" unless EM.reactor_running?
184
-
185
- # merge and format environment strings, if necessary.
186
- environment_hash = options[:environment] || {}
187
- environment_strings = RightPopenEx.merge_environment(environment_hash)
188
-
189
- # launch cmd and request asynchronous output.
190
- mode = "t"
191
- show_window = false
192
- asynchronous_output = true
193
- stream_in, stream_out, stream_err, pid = RightPopen.popen4(options[:command], mode, show_window, asynchronous_output, environment_strings)
194
-
195
- # close input immediately.
196
- stream_in.close
197
-
198
- # attach handlers to event machine and let it monitor incoming data. the
199
- # streams aren't used directly by the connectors except that they are closed
200
- # on unbind.
201
- stderr_eventable = EM.watch(stream_err, StdErrHandler, options[:target], options[:stderr_handler], stream_err) { |c| c.notify_readable = true } if options[:stderr_handler]
202
- EM.watch(stream_out, StdOutHandler, options[:target], options[:stdout_handler], options[:exit_handler], stderr_eventable, stream_out, pid) { |c| c.notify_readable = true }
203
-
204
- # note that control returns to the caller, but the launched cmd continues
205
- # running and sends output to the handlers. the caller is not responsible
206
- # for waiting for the process to terminate or closing streams as the
207
- # watched eventables will handle this automagically. notification will be
208
- # sent to the exit_handler on process termination.
209
- end
210
-
211
- protected
212
-
213
- module RightPopenEx
214
- # Key class for case-insensitive hash insertion/lookup.
215
- class NoCaseKey
216
- # Internal key
217
- attr_reader :key
218
-
219
- # Stringizes object to be used as key
220
- def initialize key
221
- @key = key.to_s
222
- end
223
-
224
- # Hash code
225
- def hash
226
- @key.downcase.hash
227
- end
228
-
229
- # Equality for hash
230
- def eql? other
231
- @key.downcase.hash == other.key.downcase.hash
232
- end
233
-
234
- # Sort operator
235
- def <=> other
236
- @key.downcase <=> other.key.downcase
237
- end
238
-
239
- # Stringizer
240
- def to_s
241
- @key
242
- end
243
-
244
- # Inspector
245
- def inspect
246
- "\"#{@key}\""
247
- end
248
- end
249
-
250
- # Hash of known environment variable keys to special merge method proc.
251
- SPECIAL_MERGE_ENV_KEY_HASH = {
252
- NoCaseKey.new('PATH') => lambda { |from_value, to_value| merge_environment_path_value(from_value, to_value) }
253
- }
254
-
255
- # Merges the given environment hash with the current environment for this
256
- # process and the current environment for the current thread user from the
257
- # registry. The result is a nul-terminated block of nul-terminated strings
258
- # suitable for use in creating the child process.
259
- #
260
- # === Parameters
261
- # environment_hash(Hash):: Hash of environment key/value pairs or empty to
262
- # only merge the current process and currend thread user environment.
263
- #
264
- # === Returns
265
- # merged string block
266
- def self.merge_environment(environment_hash)
267
- current_user_environment_hash = get_current_user_environment
268
- machine_environment_hash = get_machine_environment
269
- result_environment_hash = get_process_environment
270
-
271
- # machine from registry supercedes process.
272
- merge_environment2(machine_environment_hash, result_environment_hash)
273
-
274
- # user environment from registry supercedes machine and process.
275
- merge_environment2(current_user_environment_hash, result_environment_hash)
276
-
277
- # caller's environment supercedes all.
278
- merge_environment2(environment_hash, result_environment_hash)
279
-
280
- return environment_hash_to_string_block(result_environment_hash)
281
- end
282
-
283
- # Merges from hash to another with special handling for known env vars.
284
- #
285
- # === Parameters
286
- # from_hash(Hash):: hash of string or environment keys to environment values
287
- # to_hash(Hash):: resulting hash or environment keys to environment values
288
- #
289
- # === Returns
290
- # to_hash(Hash):: merged 'to' hash
291
- def self.merge_environment2(from_hash, to_hash)
292
- from_hash.each do |from_key, from_value|
293
- to_key = from_key.kind_of?(NoCaseKey) ?
294
- from_key :
295
- NoCaseKey.new(from_key)
296
- to_value = to_hash[to_key]
297
- if to_value
298
- special_merge_proc = SPECIAL_MERGE_ENV_KEY_HASH[to_key]
299
- if special_merge_proc
300
- # special merge
301
- to_hash[to_key] = special_merge_proc.call(from_value, to_value)
302
- else
303
- # 'from' value supercedes existing 'to' value
304
- to_hash[to_key] = from_value
305
- end
306
- else
307
- # 'from' value replaces missing 'to' value
308
- to_hash[to_key] = from_value
309
- end
310
- end
311
- end
312
-
313
- # Merges a PATH-style variable by appending any missing subpaths on the
314
- # 'to' side to the value on the 'from' side in order of appearance. note
315
- # that the ordering of paths on the 'to' side is not preserved when some of
316
- # the paths also appear on the 'from' side. This is because paths on the
317
- # 'from' side always take precedence. This is an issue if two paths
318
- # reference similarly named executables and swapping the order of paths
319
- # would cause the wrong executable to be invoked. To resolve this, the
320
- # higher precedence path can be changed to ensure that the conflicting paths
321
- # are both specified in the proper order. There is no trivial algorithm
322
- # which can predict the proper ordering of such paths.
323
- #
324
- # === Parameters
325
- # from_value(String):: value to merge from
326
- # to_value(String):: value to merge to
327
- #
328
- # === Returns
329
- # merged_value(String):: merged value
330
- def self.merge_environment_path_value(from_value, to_value)
331
- # normalize to backslashes for Windows-style PATH variable.
332
- from_value = from_value.gsub(File::SEPARATOR, File::ALT_SEPARATOR)
333
- to_value = to_value.gsub(File::SEPARATOR, File::ALT_SEPARATOR)
334
-
335
- # quick outs.
336
- return from_value if to_value.empty?
337
- return to_value if from_value.empty?
338
-
339
- # Windows paths are case-insensitive, so we want to match paths efficiently
340
- # while being case-insensitive. we will make use of NoCaseKey again.
341
- from_value_hash = {}
342
- from_value.split(File::PATH_SEPARATOR).each { |path| from_value_hash[NoCaseKey.new(path)] = true }
343
- appender = ""
344
- to_value.split(File::PATH_SEPARATOR).each do |path|
345
- if not from_value_hash[NoCaseKey.new(path)]
346
- appender += File::PATH_SEPARATOR + path
347
- end
348
- end
349
-
350
- return from_value + appender
351
- end
352
-
353
- # Queries the environment strings from the current thread/process user's
354
- # environment. The resulting hash represents any variables set for the
355
- # persisted user context but any set dynamically in the current process
356
- # context.
357
- #
358
- # === Returns
359
- # environment_hash(Hash):: hash of environment key (String) to value (String).
360
- def self.get_current_user_environment
361
- environment_strings = RightPopen.get_current_user_environment
362
-
363
- return string_block_to_environment_hash(environment_strings)
364
- end
365
-
366
- # Queries the environment strings from the machine's environment.
367
- #
368
- # === Returns
369
- # environment_hash(Hash):: hash of environment key (String) to value (String).
370
- def self.get_machine_environment
371
- environment_strings = RightPopen.get_machine_environment
372
-
373
- return string_block_to_environment_hash(environment_strings)
374
- end
375
-
376
- # Queries the environment strings from the process environment (which is kept
377
- # in memory for each process and generally begins life as a copy of the
378
- # process user's environment context plus any changes made by ancestral
379
- # processes).
380
- #
381
- # === Returns
382
- # environment_hash(Hash):: hash of environment key (String) to value (String).
383
- def self.get_process_environment
384
- environment_strings = RightPopen.get_process_environment
385
-
386
- return string_block_to_environment_hash(environment_strings)
387
- end
388
-
389
- # Converts a nul-terminated block of nul-terminated strings to a hash by
390
- # splitting the block on nul characters until the empty string is found.
391
- # splits substrings on the '=' character which is used to delimit key from
392
- # value in Windows environment blocks.
393
- #
394
- # === Paramters
395
- # string_block(String):: string containing nul-terminated substrings followed
396
- # by a nul-terminator.
397
- #
398
- # === Returns
399
- # string_hash(Hash):: hash of string to string
400
- def self.string_block_to_environment_hash(string_block)
401
- result_hash = {}
402
- last_offset = 0
403
- string_block_length = string_block.length
404
- while last_offset < string_block_length
405
- offset = string_block.index(0.chr, last_offset)
406
- if offset.nil?
407
- offset = string_block.length
408
- end
409
- env_string = string_block[last_offset, offset - last_offset]
410
- break if env_string.empty?
411
- last_offset = offset + 1
412
-
413
- # note that Windows uses "=C:=C:\" notation for working directory info, so
414
- # ignore equals if it is the first character.
415
- equals_offset = env_string.index('=', 1)
416
- if equals_offset
417
- env_key = env_string[0, equals_offset]
418
- env_value = env_string[equals_offset + 1..-1]
419
- result_hash[NoCaseKey.new(env_key)] = env_value
420
- end
421
- end
422
-
423
- return result_hash
424
- end
425
-
426
- # Converts a hash of string to string to a string block by combining pairs
427
- # into a single string delimited by the '=' character and then placing nul-
428
- # terminators after each pair, followed by a final nul-terminator.
429
- #
430
- # === Parameters
431
- # environment_hash(Hash):: hash of
432
- def self.environment_hash_to_string_block(environment_hash)
433
- result_block = ""
434
- environment_hash.keys.sort.each do |key|
435
- result_block += "#{key}=#{environment_hash[key]}\0"
436
- end
437
-
438
- return result_block + "\0"
439
- end
440
-
441
- end
442
- end
1
+ #--
2
+ # Copyright (c) 2009 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require 'rubygems'
25
+ require 'eventmachine'
26
+ require 'win32/process'
27
+
28
+ require File.join(File.dirname(__FILE__), 'right_popen.so') # win32 native code
29
+
30
+ module RightScale
31
+
32
+ # Eventmachine callback handler for stdin stream
33
+ module StdInHandler
34
+
35
+ # === Parameters
36
+ # options[:input](String):: Input to be streamed into child process stdin
37
+ # stream_in(IO):: Standard input stream.
38
+ def initialize(options, stream_in)
39
+ @stream_in = stream_in
40
+ @input = options[:input]
41
+ end
42
+
43
+ # Eventmachine callback asking for more to write
44
+ # Send input and close stream in
45
+ def post_init
46
+ if @input
47
+ send_data(@input)
48
+ close_connection_after_writing
49
+ @input = nil
50
+ else
51
+ close_connection
52
+ end
53
+ end
54
+
55
+ end
56
+
57
+ # Provides an eventmachine callback handler for the stdout stream.
58
+ module StdOutHandler
59
+
60
+ # Quacks like Process::Status, which we cannot instantiate ourselves because
61
+ # has no public new method. RightScale::popen3 needs this because the
62
+ # 'win32/process' gem currently won't return Process::Status objects but
63
+ # only returns a [pid, exitstatus] value.
64
+ class Status
65
+ # Process ID
66
+ attr_reader :pid
67
+
68
+ # Process exit code
69
+ attr_reader :exitstatus
70
+
71
+ # === Parameters
72
+ # pid(Integer):: Process ID.
73
+ #
74
+ # exitstatus(Integer):: Process exit code
75
+ def initialize(pid, exitstatus)
76
+ @pid = pid
77
+ @exitstatus = exitstatus
78
+ end
79
+
80
+ # Simulates Process::Status.exited?
81
+ #
82
+ # === Returns
83
+ # true in all cases because this object cannot be returned until the
84
+ # process exits
85
+ def exited?
86
+ return true
87
+ end
88
+
89
+ # Simulates Process::Status.success?
90
+ #
91
+ # === Returns
92
+ # true if the process returned zero as its exit code
93
+ def success?
94
+ return @exitstatus ? (0 == @exitstatus) : true;
95
+ end
96
+ end
97
+
98
+ # === Parameters
99
+ # options[:target](Object):: Object defining handler methods to be called.
100
+ # options[:stdout_handler](String):: Token for stdout handler method name.
101
+ # options[:exit_handler](String):: Token for exit handler method name.
102
+ # stderr_eventable(Connector):: EM object representing stderr handler.
103
+ # stream_out(IO):: Standard output stream.
104
+ # pid(Integer):: Child process ID.
105
+ def initialize(options, stderr_eventable, stream_out, pid)
106
+ @target = options[:target]
107
+ @stdout_handler = options[:stdout_handler]
108
+ @exit_handler = options[:exit_handler]
109
+ @stderr_eventable = stderr_eventable
110
+ @stream_out = stream_out
111
+ @pid = pid
112
+ @status = nil
113
+ end
114
+
115
+ # Callback from EM to asynchronously read the stdout stream. Note that this
116
+ # callback mechanism is deprecated after EM v0.12.8
117
+ def notify_readable
118
+ data = RightPopen.async_read(@stream_out)
119
+ receive_data(data) if (data && data.length > 0)
120
+ detach unless data
121
+ end
122
+
123
+ # Callback from EM to receive data, which we also use to handle the
124
+ # asynchronous data we read ourselves.
125
+ def receive_data(data)
126
+ @target.method(@stdout_handler).call(data) if @stdout_handler
127
+ end
128
+
129
+ # Override of Connection.get_status() for Windows implementation.
130
+ def get_status
131
+ unless @status
132
+ begin
133
+ @status = Status.new(@pid, Process.waitpid2(@pid)[1])
134
+ rescue Process::Error
135
+ # process is gone, which means we have no recourse to retrieve the
136
+ # actual exit code; let's be optimistic.
137
+ @status = Status.new(@pid, 0)
138
+ end
139
+ end
140
+ return @status
141
+ end
142
+
143
+ # Callback from EM to unbind.
144
+ def unbind
145
+ # We force the stderr watched handler to go away so that
146
+ # we don't end up with a broken pipe
147
+ @stderr_eventable.force_detach if @stderr_eventable
148
+ @target.method(@exit_handler).call(get_status) if @exit_handler
149
+ @stream_out.close
150
+ end
151
+ end
152
+
153
+ # Provides an eventmachine callback handler for the stderr stream.
154
+ module StdErrHandler
155
+
156
+ # === Parameters
157
+ # options[:target](Object):: Object defining handler methods to be called.
158
+ # options[:stderr_handler](Symbol):: Token for stderr handler method name.
159
+ # stream_err(IO):: Standard error stream.
160
+ def initialize(options, stream_err)
161
+ @target = options[:target]
162
+ @stderr_handler = options[:stderr_handler]
163
+ @stream_err = stream_err
164
+ @unbound = false
165
+ end
166
+
167
+ # Callback from EM to asynchronously read the stderr stream. Note that this
168
+ # callback mechanism is deprecated after EM v0.12.8
169
+ def notify_readable
170
+ data = RightPopen.async_read(@stream_err)
171
+ receive_data(data) if (data && data.length > 0)
172
+ detach unless data
173
+ end
174
+
175
+ # Callback from EM to receive data, which we also use to handle the
176
+ # asynchronous data we read ourselves.
177
+ def receive_data(data)
178
+ @target.method(@stderr_handler).call(data)
179
+ end
180
+
181
+ # Callback from EM to unbind.
182
+ def unbind
183
+ @unbound = true
184
+ @stream_err.close
185
+ end
186
+
187
+ # Forces detachment of the stderr handler on EM's next tick.
188
+ def force_detach
189
+ # Use next tick to prevent issue in EM where descriptors list
190
+ # gets out-of-sync when calling detach in an unbind callback
191
+ EM.next_tick { detach unless @unbound }
192
+ end
193
+ end
194
+
195
+ # Creates a child process and connects event handlers to the standard output
196
+ # and error streams used by the created process. Connectors use named pipes
197
+ # and asynchronous I/O in the native Windows implementation.
198
+ #
199
+ # See RightScale.popen3
200
+ def self.popen3_imp(options)
201
+ raise "EventMachine reactor must be started" unless EM.reactor_running?
202
+
203
+ # merge and format environment strings, if necessary.
204
+ environment_hash = options[:environment] || {}
205
+ environment_strings = RightPopenEx.merge_environment(environment_hash)
206
+
207
+ # launch cmd and request asynchronous output.
208
+ mode = "t"
209
+ show_window = false
210
+ asynchronous_output = true
211
+ stream_in, stream_out, stream_err, pid = RightPopen.popen4(options[:command], mode, show_window, asynchronous_output, environment_strings)
212
+
213
+ # close input immediately.
214
+ stream_in.close if options[:input].nil?
215
+
216
+ # attach handlers to event machine and let it monitor incoming data. the
217
+ # streams aren't used directly by the connectors except that they are closed
218
+ # on unbind.
219
+ stderr_eventable = EM.watch(stream_err, StdErrHandler, options, stream_err) { |c| c.notify_readable = true } if options[:stderr_handler]
220
+ EM.watch(stream_out, StdOutHandler, options, stderr_eventable, stream_out, pid) { |c| c.notify_readable = true }
221
+ EM.attach(stream_in, StdInHandler, options, stream_in) if options[:input]
222
+
223
+ # note that control returns to the caller, but the launched cmd continues
224
+ # running and sends output to the handlers. the caller is not responsible
225
+ # for waiting for the process to terminate or closing streams as the
226
+ # watched eventables will handle this automagically. notification will be
227
+ # sent to the exit_handler on process termination.
228
+ end
229
+
230
+ protected
231
+
232
+ module RightPopenEx
233
+ # Key class for case-insensitive hash insertion/lookup.
234
+ class NoCaseKey
235
+ # Internal key
236
+ attr_reader :key
237
+
238
+ # Stringizes object to be used as key
239
+ def initialize key
240
+ @key = key.to_s
241
+ end
242
+
243
+ # Hash code
244
+ def hash
245
+ @key.downcase.hash
246
+ end
247
+
248
+ # Equality for hash
249
+ def eql? other
250
+ @key.downcase.hash == other.key.downcase.hash
251
+ end
252
+
253
+ # Sort operator
254
+ def <=> other
255
+ @key.downcase <=> other.key.downcase
256
+ end
257
+
258
+ # Stringizer
259
+ def to_s
260
+ @key
261
+ end
262
+
263
+ # Inspector
264
+ def inspect
265
+ "\"#{@key}\""
266
+ end
267
+ end
268
+
269
+ # Hash of known environment variable keys to special merge method proc.
270
+ SPECIAL_MERGE_ENV_KEY_HASH = {
271
+ NoCaseKey.new('PATH') => lambda { |from_value, to_value| merge_environment_path_value(from_value, to_value) }
272
+ }
273
+
274
+ # Merges the given environment hash with the current environment for this
275
+ # process and the current environment for the current thread user from the
276
+ # registry. The result is a nul-terminated block of nul-terminated strings
277
+ # suitable for use in creating the child process.
278
+ #
279
+ # === Parameters
280
+ # environment_hash(Hash):: Hash of environment key/value pairs or empty to
281
+ # only merge the current process and currend thread user environment.
282
+ #
283
+ # === Returns
284
+ # merged string block
285
+ def self.merge_environment(environment_hash)
286
+ current_user_environment_hash = get_current_user_environment
287
+ machine_environment_hash = get_machine_environment
288
+ result_environment_hash = get_process_environment
289
+
290
+ # machine from registry supercedes process.
291
+ merge_environment2(machine_environment_hash, result_environment_hash)
292
+
293
+ # user environment from registry supercedes machine and process.
294
+ merge_environment2(current_user_environment_hash, result_environment_hash)
295
+
296
+ # caller's environment supercedes all.
297
+ merge_environment2(environment_hash, result_environment_hash)
298
+
299
+ return environment_hash_to_string_block(result_environment_hash)
300
+ end
301
+
302
+ # Merges from hash to another with special handling for known env vars.
303
+ #
304
+ # === Parameters
305
+ # from_hash(Hash):: hash of string or environment keys to environment values
306
+ # to_hash(Hash):: resulting hash or environment keys to environment values
307
+ #
308
+ # === Returns
309
+ # to_hash(Hash):: merged 'to' hash
310
+ def self.merge_environment2(from_hash, to_hash)
311
+ from_hash.each do |from_key, from_value|
312
+ to_key = from_key.kind_of?(NoCaseKey) ?
313
+ from_key :
314
+ NoCaseKey.new(from_key)
315
+ to_value = to_hash[to_key]
316
+ if to_value
317
+ special_merge_proc = SPECIAL_MERGE_ENV_KEY_HASH[to_key]
318
+ if special_merge_proc
319
+ # special merge
320
+ to_hash[to_key] = special_merge_proc.call(from_value, to_value)
321
+ else
322
+ # 'from' value supercedes existing 'to' value
323
+ to_hash[to_key] = from_value
324
+ end
325
+ else
326
+ # 'from' value replaces missing 'to' value
327
+ to_hash[to_key] = from_value
328
+ end
329
+ end
330
+ end
331
+
332
+ # Merges a PATH-style variable by appending any missing subpaths on the
333
+ # 'to' side to the value on the 'from' side in order of appearance. note
334
+ # that the ordering of paths on the 'to' side is not preserved when some of
335
+ # the paths also appear on the 'from' side. This is because paths on the
336
+ # 'from' side always take precedence. This is an issue if two paths
337
+ # reference similarly named executables and swapping the order of paths
338
+ # would cause the wrong executable to be invoked. To resolve this, the
339
+ # higher precedence path can be changed to ensure that the conflicting paths
340
+ # are both specified in the proper order. There is no trivial algorithm
341
+ # which can predict the proper ordering of such paths.
342
+ #
343
+ # === Parameters
344
+ # from_value(String):: value to merge from
345
+ # to_value(String):: value to merge to
346
+ #
347
+ # === Returns
348
+ # merged_value(String):: merged value
349
+ def self.merge_environment_path_value(from_value, to_value)
350
+ # normalize to backslashes for Windows-style PATH variable.
351
+ from_value = from_value.gsub(File::SEPARATOR, File::ALT_SEPARATOR)
352
+ to_value = to_value.gsub(File::SEPARATOR, File::ALT_SEPARATOR)
353
+
354
+ # quick outs.
355
+ return from_value if to_value.empty?
356
+ return to_value if from_value.empty?
357
+
358
+ # Windows paths are case-insensitive, so we want to match paths efficiently
359
+ # while being case-insensitive. we will make use of NoCaseKey again.
360
+ from_value_hash = {}
361
+ from_value.split(File::PATH_SEPARATOR).each { |path| from_value_hash[NoCaseKey.new(path)] = true }
362
+ appender = ""
363
+ to_value.split(File::PATH_SEPARATOR).each do |path|
364
+ if not from_value_hash[NoCaseKey.new(path)]
365
+ appender += File::PATH_SEPARATOR + path
366
+ end
367
+ end
368
+
369
+ return from_value + appender
370
+ end
371
+
372
+ # Queries the environment strings from the current thread/process user's
373
+ # environment. The resulting hash represents any variables set for the
374
+ # persisted user context but any set dynamically in the current process
375
+ # context.
376
+ #
377
+ # === Returns
378
+ # environment_hash(Hash):: hash of environment key (String) to value (String).
379
+ def self.get_current_user_environment
380
+ environment_strings = RightPopen.get_current_user_environment
381
+
382
+ return string_block_to_environment_hash(environment_strings)
383
+ end
384
+
385
+ # Queries the environment strings from the machine's environment.
386
+ #
387
+ # === Returns
388
+ # environment_hash(Hash):: hash of environment key (String) to value (String).
389
+ def self.get_machine_environment
390
+ environment_strings = RightPopen.get_machine_environment
391
+
392
+ return string_block_to_environment_hash(environment_strings)
393
+ end
394
+
395
+ # Queries the environment strings from the process environment (which is kept
396
+ # in memory for each process and generally begins life as a copy of the
397
+ # process user's environment context plus any changes made by ancestral
398
+ # processes).
399
+ #
400
+ # === Returns
401
+ # environment_hash(Hash):: hash of environment key (String) to value (String).
402
+ def self.get_process_environment
403
+ environment_strings = RightPopen.get_process_environment
404
+
405
+ return string_block_to_environment_hash(environment_strings)
406
+ end
407
+
408
+ # Converts a nul-terminated block of nul-terminated strings to a hash by
409
+ # splitting the block on nul characters until the empty string is found.
410
+ # splits substrings on the '=' character which is used to delimit key from
411
+ # value in Windows environment blocks.
412
+ #
413
+ # === Paramters
414
+ # string_block(String):: string containing nul-terminated substrings followed
415
+ # by a nul-terminator.
416
+ #
417
+ # === Returns
418
+ # string_hash(Hash):: hash of string to string
419
+ def self.string_block_to_environment_hash(string_block)
420
+ result_hash = {}
421
+ last_offset = 0
422
+ string_block_length = string_block.length
423
+ while last_offset < string_block_length
424
+ offset = string_block.index(0.chr, last_offset)
425
+ if offset.nil?
426
+ offset = string_block.length
427
+ end
428
+ env_string = string_block[last_offset, offset - last_offset]
429
+ break if env_string.empty?
430
+ last_offset = offset + 1
431
+
432
+ # note that Windows uses "=C:=C:\" notation for working directory info, so
433
+ # ignore equals if it is the first character.
434
+ equals_offset = env_string.index('=', 1)
435
+ if equals_offset
436
+ env_key = env_string[0, equals_offset]
437
+ env_value = env_string[equals_offset + 1..-1]
438
+ result_hash[NoCaseKey.new(env_key)] = env_value
439
+ end
440
+ end
441
+
442
+ return result_hash
443
+ end
444
+
445
+ # Converts a hash of string to string to a string block by combining pairs
446
+ # into a single string delimited by the '=' character and then placing nul-
447
+ # terminators after each pair, followed by a final nul-terminator.
448
+ #
449
+ # === Parameters
450
+ # environment_hash(Hash):: hash of
451
+ def self.environment_hash_to_string_block(environment_hash)
452
+ result_block = ""
453
+ environment_hash.keys.sort.each do |key|
454
+ result_block += "#{key}=#{environment_hash[key]}\0"
455
+ end
456
+
457
+ return result_block + "\0"
458
+ end
459
+
460
+ end
461
+ end