ohai 0.5.8 → 0.6.0.beta.0

Sign up to get free protection for your applications and to get access to all the features.
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