ohai 0.5.8 → 0.6.0.beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. data/Rakefile +16 -48
  2. data/bin/ohai +1 -1
  3. data/lib/ohai.rb +1 -3
  4. data/lib/ohai/mash.rb +211 -0
  5. data/lib/ohai/mixin/command.rb +157 -44
  6. data/lib/ohai/mixin/ec2_metadata.rb +87 -0
  7. data/lib/ohai/plugins/c.rb +16 -13
  8. data/lib/ohai/plugins/chef.rb +2 -1
  9. data/lib/ohai/plugins/cloud.rb +25 -0
  10. data/lib/ohai/plugins/darwin/network.rb +10 -1
  11. data/lib/ohai/plugins/dmi.rb +100 -37
  12. data/lib/ohai/plugins/dmi_common.rb +117 -0
  13. data/lib/ohai/plugins/ec2.rb +12 -61
  14. data/lib/ohai/plugins/eucalyptus.rb +65 -0
  15. data/lib/ohai/plugins/freebsd/network.rb +11 -2
  16. data/lib/ohai/plugins/java.rb +4 -4
  17. data/lib/ohai/plugins/linux/filesystem.rb +24 -0
  18. data/lib/ohai/plugins/linux/network.rb +14 -1
  19. data/lib/ohai/plugins/linux/platform.rb +3 -0
  20. data/lib/ohai/plugins/linux/virtualization.rb +28 -6
  21. data/lib/ohai/plugins/netbsd/network.rb +10 -1
  22. data/lib/ohai/plugins/network.rb +3 -1
  23. data/lib/ohai/plugins/ohai.rb +1 -0
  24. data/lib/ohai/plugins/openbsd/network.rb +10 -1
  25. data/lib/ohai/plugins/ruby.rb +1 -1
  26. data/lib/ohai/plugins/sigar/filesystem.rb +2 -0
  27. data/lib/ohai/plugins/solaris2/dmi.rb +176 -0
  28. data/lib/ohai/plugins/solaris2/filesystem.rb +101 -0
  29. data/lib/ohai/plugins/solaris2/hostname.rb +10 -1
  30. data/lib/ohai/plugins/solaris2/network.rb +6 -1
  31. data/lib/ohai/plugins/solaris2/uptime.rb +36 -0
  32. data/lib/ohai/plugins/solaris2/virtualization.rb +91 -0
  33. data/lib/ohai/plugins/virtualization.rb +1 -1
  34. data/lib/ohai/plugins/windows/network.rb +17 -11
  35. data/lib/ohai/system.rb +22 -32
  36. data/lib/ohai/version.rb +23 -0
  37. data/spec/ohai/plugins/c_spec.rb +58 -7
  38. data/spec/ohai/plugins/cloud_spec.rb +24 -0
  39. data/spec/ohai/plugins/dmi_spec.rb +107 -47
  40. data/spec/ohai/plugins/ec2_spec.rb +3 -3
  41. data/spec/ohai/plugins/eucalyptus_spec.rb +84 -0
  42. data/spec/ohai/plugins/java_spec.rb +55 -2
  43. data/spec/ohai/plugins/linux/platform_spec.rb +13 -0
  44. data/spec/ohai/plugins/linux/virtualization_spec.rb +47 -6
  45. data/spec/ohai/plugins/perl_spec.rb +15 -14
  46. data/spec/ohai/plugins/rackspace_spec.rb +14 -14
  47. data/spec/ohai/plugins/solaris2/hostname_spec.rb +3 -8
  48. data/spec/ohai/plugins/solaris2/kernel_spec.rb +6 -6
  49. data/spec/ohai/plugins/solaris2/network_spec.rb +22 -22
  50. data/spec/ohai/plugins/solaris2/virtualization_spec.rb +133 -0
  51. data/spec/spec_helper.rb +5 -13
  52. metadata +182 -179
data/Rakefile CHANGED
@@ -2,61 +2,29 @@ require 'rubygems'
2
2
  require 'rake/gempackagetask'
3
3
  require 'rubygems/specification'
4
4
  require 'date'
5
- require 'spec/rake/spectask'
6
5
 
7
- GEM = "ohai"
8
- GEM_VERSION = "0.5.8"
9
- AUTHOR = "Adam Jacob"
10
- EMAIL = "adam@opscode.com"
11
- HOMEPAGE = "http://wiki.opscode.com/display/ohai"
12
- SUMMARY = "Ohai profiles your system and emits JSON"
6
+ gemspec = eval(IO.read("ohai.gemspec"))
13
7
 
14
- spec = Gem::Specification.new do |s|
15
- s.name = GEM
16
- s.version = GEM_VERSION
17
- s.platform = Gem::Platform::RUBY
18
- s.has_rdoc = true
19
- s.summary = SUMMARY
20
- s.description = s.summary
21
- s.author = AUTHOR
22
- s.email = EMAIL
23
- s.homepage = HOMEPAGE
24
-
25
- s.add_dependency "json", ">= 1.4.4", "<= 1.4.6"
26
- s.add_dependency "extlib"
27
- s.add_dependency "systemu"
28
- s.add_dependency "mixlib-cli"
29
- s.add_dependency "mixlib-config"
30
- s.add_dependency "mixlib-log"
31
- s.bindir = "bin"
32
- s.executables = %w(ohai)
33
-
34
- s.require_path = 'lib'
35
- s.autorequire = GEM
36
- s.files = %w(LICENSE README.rdoc Rakefile) + Dir.glob("{docs,lib,spec}/**/*")
37
- end
38
-
39
- task :default => :spec
40
-
41
- desc "Run specs"
42
- Spec::Rake::SpecTask.new do |t|
43
- t.spec_files = FileList['spec/**/*_spec.rb']
44
- t.spec_opts = %w(-fs --color)
45
- end
46
8
 
47
-
48
- Rake::GemPackageTask.new(spec) do |pkg|
49
- pkg.gem_spec = spec
50
- end
9
+ Rake::GemPackageTask.new(gemspec).define
51
10
 
52
11
  desc "install the gem locally"
53
12
  task :install => [:package] do
54
- sh %{gem install pkg/#{GEM}-#{GEM_VERSION}}
13
+ sh %{gem install pkg/#{ohai}-#{OHAI_VERSION}}
55
14
  end
56
15
 
57
- desc "create a gemspec file"
58
- task :make_spec do
59
- File.open("#{GEM}.gemspec", "w") do |file|
60
- file.puts spec.to_ruby
16
+ begin
17
+ require 'rspec/core/rake_task'
18
+
19
+ RSpec::Core::RakeTask.new do |t|
20
+ t.pattern = 'spec/**/*_spec.rb'
21
+ t.rspec_opts = %w(-fs --color)
22
+ end
23
+ rescue LoadError
24
+ desc "rspec is not installed, this task is disabled"
25
+ task :spec do
26
+ abort "rspec is not installed. `(sudo) gem install rspec` to run unit tests"
61
27
  end
62
28
  end
29
+
30
+ task :default => :spec
data/bin/ohai CHANGED
@@ -29,7 +29,7 @@ end
29
29
  begin
30
30
  # if we're in a source code checkout, we want to run the code from that.
31
31
  # have to do this *after* rubygems is loaded.
32
- $:.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib'))
32
+ $:.unshift File.expand_path('../../lib', __FILE__)
33
33
  require 'ohai/application'
34
34
  rescue LoadError
35
35
  if missing_rubygems
data/lib/ohai.rb CHANGED
@@ -16,9 +16,7 @@
16
16
  # limitations under the License.
17
17
  #
18
18
 
19
+ require 'ohai/version'
19
20
  require 'ohai/config'
20
21
  require 'ohai/system'
21
22
 
22
- module Ohai
23
- VERSION = '0.5.8'
24
- end
data/lib/ohai/mash.rb ADDED
@@ -0,0 +1,211 @@
1
+ # Copyright (c) 2009 Dan Kubb
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
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ # ---
23
+ # ---
24
+
25
+ # Some portions of blank.rb and mash.rb are verbatim copies of software
26
+ # licensed under the MIT license. That license is included below:
27
+
28
+ # Copyright (c) 2005-2008 David Heinemeier Hansson
29
+
30
+ # Permission is hereby granted, free of charge, to any person obtaining
31
+ # a copy of this software and associated documentation files (the
32
+ # "Software"), to deal in the Software without restriction, including
33
+ # without limitation the rights to use, copy, modify, merge, publish,
34
+ # distribute, sublicense, and/or sell copies of the Software, and to
35
+ # permit persons to whom the Software is furnished to do so, subject to
36
+ # the following conditions:
37
+
38
+ # The above copyright notice and this permission notice shall be
39
+ # included in all copies or substantial portions of the Software.
40
+
41
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
42
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
43
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
44
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
45
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
46
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
47
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
48
+
49
+ # This class has dubious semantics and we only have it so that people can write
50
+ # params[:key] instead of params['key'].
51
+ class Mash < Hash
52
+
53
+ # @param constructor<Object>
54
+ # The default value for the mash. Defaults to an empty hash.
55
+ #
56
+ # @details [Alternatives]
57
+ # If constructor is a Hash, a new mash will be created based on the keys of
58
+ # the hash and no default value will be set.
59
+ def initialize(constructor = {})
60
+ if constructor.is_a?(Hash)
61
+ super()
62
+ update(constructor)
63
+ else
64
+ super(constructor)
65
+ end
66
+ end
67
+
68
+ # @param key<Object> The default value for the mash. Defaults to nil.
69
+ #
70
+ # @details [Alternatives]
71
+ # If key is a Symbol and it is a key in the mash, then the default value will
72
+ # be set to the value matching the key.
73
+ def default(key = nil)
74
+ if key.is_a?(Symbol) && include?(key = key.to_s)
75
+ self[key]
76
+ else
77
+ super
78
+ end
79
+ end
80
+
81
+ alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
82
+ alias_method :regular_update, :update unless method_defined?(:regular_update)
83
+
84
+ # @param key<Object> The key to set.
85
+ # @param value<Object>
86
+ # The value to set the key to.
87
+ #
88
+ # @see Mash#convert_key
89
+ # @see Mash#convert_value
90
+ def []=(key, value)
91
+ regular_writer(convert_key(key), convert_value(value))
92
+ end
93
+
94
+ # @param other_hash<Hash>
95
+ # A hash to update values in the mash with. The keys and the values will be
96
+ # converted to Mash format.
97
+ #
98
+ # @return [Mash] The updated mash.
99
+ def update(other_hash)
100
+ other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
101
+ self
102
+ end
103
+
104
+ alias_method :merge!, :update
105
+
106
+ # @param key<Object> The key to check for. This will be run through convert_key.
107
+ #
108
+ # @return [Boolean] True if the key exists in the mash.
109
+ def key?(key)
110
+ super(convert_key(key))
111
+ end
112
+
113
+ # def include? def has_key? def member?
114
+ alias_method :include?, :key?
115
+ alias_method :has_key?, :key?
116
+ alias_method :member?, :key?
117
+
118
+ # @param key<Object> The key to fetch. This will be run through convert_key.
119
+ # @param *extras<Array> Default value.
120
+ #
121
+ # @return [Object] The value at key or the default value.
122
+ def fetch(key, *extras)
123
+ super(convert_key(key), *extras)
124
+ end
125
+
126
+ # @param *indices<Array>
127
+ # The keys to retrieve values for. These will be run through +convert_key+.
128
+ #
129
+ # @return [Array] The values at each of the provided keys
130
+ def values_at(*indices)
131
+ indices.collect {|key| self[convert_key(key)]}
132
+ end
133
+
134
+ # @param hash<Hash> The hash to merge with the mash.
135
+ #
136
+ # @return [Mash] A new mash with the hash values merged in.
137
+ def merge(hash)
138
+ self.dup.update(hash)
139
+ end
140
+
141
+ # @param key<Object>
142
+ # The key to delete from the mash.\
143
+ def delete(key)
144
+ super(convert_key(key))
145
+ end
146
+
147
+ # @param *rejected<Array[(String, Symbol)] The mash keys to exclude.
148
+ #
149
+ # @return [Mash] A new mash without the selected keys.
150
+ #
151
+ # @example
152
+ # { :one => 1, :two => 2, :three => 3 }.except(:one)
153
+ # #=> { "two" => 2, "three" => 3 }
154
+ def except(*keys)
155
+ super(*keys.map {|k| convert_key(k)})
156
+ end
157
+
158
+ # Used to provide the same interface as Hash.
159
+ #
160
+ # @return [Mash] This mash unchanged.
161
+ def stringify_keys!; self end
162
+
163
+ # @return [Hash] The mash as a Hash with symbolized keys.
164
+ def symbolize_keys
165
+ h = Hash.new(default)
166
+ each { |key, val| h[key.to_sym] = val }
167
+ h
168
+ end
169
+
170
+ # @return [Hash] The mash as a Hash with string keys.
171
+ def to_hash
172
+ Hash.new(default).merge(self)
173
+ end
174
+
175
+ # @return [Mash] Convert a Hash into a Mash
176
+ # The input Hash's default value is maintained
177
+ def self.from_hash(hash)
178
+ mash = Mash.new(hash)
179
+ mash.default = hash.default
180
+ mash
181
+ end
182
+
183
+ protected
184
+ # @param key<Object> The key to convert.
185
+ #
186
+ # @param [Object]
187
+ # The converted key. If the key was a symbol, it will be converted to a
188
+ # string.
189
+ #
190
+ # @api private
191
+ def convert_key(key)
192
+ key.kind_of?(Symbol) ? key.to_s : key
193
+ end
194
+
195
+ # @param value<Object> The value to convert.
196
+ #
197
+ # @return [Object]
198
+ # The converted value. A Hash or an Array of hashes, will be converted to
199
+ # their Mash equivalents.
200
+ #
201
+ # @api private
202
+ def convert_value(value)
203
+ if value.class == Hash
204
+ Mash.from_hash(value)
205
+ elsif value.is_a?(Array)
206
+ value.collect { |e| convert_value(e) }
207
+ else
208
+ value
209
+ end
210
+ end
211
+ end
@@ -6,9 +6,9 @@
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
7
7
  # you may not use this file except in compliance with the License.
8
8
  # You may obtain a copy of the License at
9
- #
9
+ #
10
10
  # http://www.apache.org/licenses/LICENSE-2.0
11
- #
11
+ #
12
12
  # Unless required by applicable law or agreed to in writing, software
13
13
  # distributed under the License is distributed on an "AS IS" BASIS,
14
14
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -27,42 +27,30 @@ require 'systemu'
27
27
  module Ohai
28
28
  module Mixin
29
29
  module Command
30
-
31
- def run_command(args={})
30
+
31
+ def run_command(args={})
32
32
  if args.has_key?(:creates)
33
33
  if File.exists?(args[:creates])
34
34
  Ohai::Log.debug("Skipping #{args[:command]} - creates #{args[:creates]} exists.")
35
35
  return false
36
36
  end
37
37
  end
38
-
38
+
39
39
  stdout_string = nil
40
40
  stderr_string = nil
41
-
42
- args[:cwd] ||= Dir.tmpdir
41
+
42
+ args[:cwd] ||= Dir.tmpdir
43
43
  unless File.directory?(args[:cwd])
44
44
  raise Ohai::Exceptions::Exec, "#{args[:cwd]} does not exist or is not a directory"
45
45
  end
46
-
46
+
47
47
  status = nil
48
48
  Dir.chdir(args[:cwd]) do
49
- if args[:timeout]
50
- begin
51
- Timeout.timeout(args[:timeout]) do
52
- status, stdout_string, stderr_string = systemu(args[:command])
53
- end
54
- rescue Exception => e
55
- Ohai::Log.error("#{args[:command_string]} exceeded timeout #{args[:timeout]}")
56
- raise(e)
57
- end
58
- else
59
- status, stdout_string, stderr_string = systemu(args[:command])
60
- end
61
-
49
+ status, stdout_string, stderr_string = run_command_backend(args[:command], args[:timeout])
62
50
  # systemu returns 42 when it hits unexpected errors
63
51
  if status.exitstatus == 42 and stderr_string == ""
64
52
  stderr_string = "Failed to run: #{args[:command]}, assuming command not found"
65
- Ohai::Log.debug(stderr_string)
53
+ Ohai::Log.debug(stderr_string)
66
54
  end
67
55
 
68
56
  if stdout_string
@@ -75,7 +63,7 @@ module Ohai
75
63
  Ohai::Log.debug(stderr_string.strip)
76
64
  Ohai::Log.debug("---- End #{args[:command]} STDERR ----")
77
65
  end
78
-
66
+
79
67
  args[:returns] ||= 0
80
68
  args[:no_status_check] ||= false
81
69
  if status.exitstatus != args[:returns] and not args[:no_status_check]
@@ -88,16 +76,66 @@ module Ohai
88
76
  end
89
77
 
90
78
  module_function :run_command
91
-
92
- # This is taken directly from Ara T Howard's Open4 library, and then
79
+
80
+ def run_command_unix(command, timeout)
81
+ stderr_string, stdout_string, status = "", "", nil
82
+
83
+ exec_processing_block = lambda do |pid, stdin, stdout, stderr|
84
+ stdout_string, stderr_string = stdout.string.chomp, stderr.string.chomp
85
+ end
86
+
87
+ if timeout
88
+ begin
89
+ Timeout.timeout(timeout) do
90
+ status = popen4(command, {}, &exec_processing_block)
91
+ end
92
+ rescue Timeout::Error => e
93
+ Chef::Log.error("#{command} exceeded timeout #{timeout}")
94
+ raise(e)
95
+ end
96
+ else
97
+ status = popen4(command, {}, &exec_processing_block)
98
+ end
99
+ return status, stdout_string, stderr_string
100
+ end
101
+
102
+ def run_comand_windows(command, timeout)
103
+ if timeout
104
+ begin
105
+ systemu(command)
106
+ rescue SystemExit => e
107
+ raise
108
+ rescue Timeout::Error => e
109
+ Ohai::Log.error("#{command} exceeded timeout #{timeout}")
110
+ raise(e)
111
+ end
112
+ else
113
+ systemu(command)
114
+ end
115
+ end
116
+
117
+ if RUBY_PLATFORM =~ /mswin|mingw32|windows/
118
+ alias :run_command_backend :run_command_windows
119
+ else
120
+ alias :run_command_backend :run_command_unix
121
+ end
122
+ # This is taken directly from Ara T Howard's Open4 library, and then
93
123
  # modified to suit the needs of Ohai. Any bugs here are most likely
94
124
  # my own, and not Ara's.
95
125
  #
96
- # The original appears in external/open4.rb in its unmodified form.
126
+ # The original appears in external/open4.rb in its unmodified form.
97
127
  #
98
128
  # Thanks Ara!
99
129
  def popen4(cmd, args={}, &b)
100
-
130
+
131
+ # Waitlast - this is magic.
132
+ #
133
+ # Do we wait for the child process to die before we yield
134
+ # to the block, or after? That is the magic of waitlast.
135
+ #
136
+ # By default, we are waiting before we yield the block.
137
+ args[:waitlast] ||= false
138
+
101
139
  args[:user] ||= nil
102
140
  unless args[:user].kind_of?(Integer)
103
141
  args[:user] = Etc.getpwnam(args[:user]).uid if args[:user]
@@ -114,7 +152,7 @@ module Ohai
114
152
  unless args[:environment].has_key?("LC_ALL")
115
153
  args[:environment]["LC_ALL"] = "C"
116
154
  end
117
-
155
+
118
156
  pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
119
157
 
120
158
  verbose = $VERBOSE
@@ -123,6 +161,8 @@ module Ohai
123
161
  ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
124
162
 
125
163
  cid = fork {
164
+ Process.setsid
165
+
126
166
  pw.last.close
127
167
  STDIN.reopen pw.first
128
168
  pw.first.close
@@ -137,27 +177,32 @@ module Ohai
137
177
 
138
178
  STDOUT.sync = STDERR.sync = true
139
179
 
140
- if args[:user]
141
- Process.euid = args[:user]
142
- Process.uid = args[:user]
143
- end
144
-
145
180
  if args[:group]
146
181
  Process.egid = args[:group]
147
182
  Process.gid = args[:group]
148
183
  end
149
-
184
+
185
+ if args[:user]
186
+ Process.euid = args[:user]
187
+ Process.uid = args[:user]
188
+ end
189
+
150
190
  args[:environment].each do |key,value|
151
191
  ENV[key] = value
152
192
  end
153
-
193
+
194
+ if args[:umask]
195
+ umask = ((args[:umask].respond_to?(:oct) ? args[:umask].oct : args[:umask].to_i) & 007777)
196
+ File.umask(umask)
197
+ end
198
+
154
199
  begin
155
200
  if cmd.kind_of?(Array)
156
201
  exec(*cmd)
157
202
  else
158
203
  exec(cmd)
159
204
  end
160
- raise 'forty-two'
205
+ raise 'forty-two'
161
206
  rescue Exception => e
162
207
  Marshal.dump(e, ps.last)
163
208
  ps.last.flush
@@ -173,9 +218,6 @@ module Ohai
173
218
 
174
219
  begin
175
220
  e = Marshal.load ps.first
176
- # If we get here, exec failed. Collect status of child to prevent
177
- # zombies.
178
- Process.waitpid(cid)
179
221
  raise(Exception === e ? e : "unknown failure!")
180
222
  rescue EOFError # If we get an EOF error, then the exec was successful
181
223
  42
@@ -187,18 +229,89 @@ module Ohai
187
229
 
188
230
  pi = [pw.last, pr.first, pe.first]
189
231
 
190
- if b
232
+ if b
191
233
  begin
192
- b[cid, *pi]
193
- Process.waitpid2(cid).last
234
+ if args[:waitlast]
235
+ b[cid, *pi]
236
+ # send EOF so that if the child process is reading from STDIN
237
+ # it will actually finish up and exit
238
+ pi[0].close_write
239
+ Process.waitpid2(cid).last
240
+ else
241
+ # This took some doing.
242
+ # The trick here is to close STDIN
243
+ # Then set our end of the childs pipes to be O_NONBLOCK
244
+ # Then wait for the child to die, which means any IO it
245
+ # wants to do must be done - it's dead. If it isn't,
246
+ # it's because something totally skanky is happening,
247
+ # and we don't care.
248
+ o = StringIO.new
249
+ e = StringIO.new
250
+
251
+ #pi[0].close
252
+
253
+ stdout = pi[1]
254
+ stderr = pi[2]
255
+
256
+ stdout.sync = true
257
+ stderr.sync = true
258
+
259
+ stdout.fcntl(Fcntl::F_SETFL, pi[1].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
260
+ stderr.fcntl(Fcntl::F_SETFL, pi[2].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
261
+
262
+ stdout_finished = false
263
+ stderr_finished = false
264
+
265
+ results = nil
266
+
267
+ while !stdout_finished || !stderr_finished
268
+ begin
269
+ channels_to_watch = []
270
+ channels_to_watch << stdout if !stdout_finished
271
+ channels_to_watch << stderr if !stderr_finished
272
+ ready = IO.select(channels_to_watch, nil, nil, 1.0)
273
+ rescue Errno::EAGAIN
274
+ ensure
275
+ results = Process.waitpid2(cid, Process::WNOHANG)
276
+ if results
277
+ stdout_finished = true
278
+ stderr_finished = true
279
+ end
280
+ end
281
+
282
+ if ready && ready.first.include?(stdout)
283
+ line = results ? stdout.gets(nil) : stdout.gets
284
+ if line
285
+ o.write(line)
286
+ else
287
+ stdout_finished = true
288
+ end
289
+ end
290
+ if ready && ready.first.include?(stderr)
291
+ line = results ? stderr.gets(nil) : stderr.gets
292
+ if line
293
+ e.write(line)
294
+ else
295
+ stderr_finished = true
296
+ end
297
+ end
298
+ end
299
+ results = Process.waitpid2(cid) unless results
300
+ o.rewind
301
+ e.rewind
302
+ b[cid, pi[0], o, e]
303
+ results.last
304
+ end
194
305
  ensure
195
306
  pi.each{|fd| fd.close unless fd.closed?}
196
307
  end
197
308
  else
198
309
  [cid, pw.last, pr.first, pe.first]
199
310
  end
200
- end
201
-
311
+ rescue Errno::ENOENT
312
+ raise Ohai::Exceptions::Exec, "command #{cmd} doesn't exist or is not in the PATH"
313
+ end
314
+
202
315
  module_function :popen4
203
316
  end
204
317
  end