1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
# An actual fact resolution mechanism. These are largely just chunks of
# code, with optional confinements restricting the mechanisms to only working on
# specific systems. Note that the confinements are always ANDed, so any
# confinements specified must all be true for the resolution to be
# suitable.
require 'facter/util/confine'
require 'timeout'
require 'rbconfig'
class Facter::Util::Resolution
attr_accessor :interpreter, :code, :name, :timeout
def self.have_which
if ! defined?(@have_which) or @have_which.nil?
if Config::CONFIG['host_os'] =~ /mswin/
@have_which = false
else
%x{which which 2>/dev/null}
@have_which = ($? == 0)
end
end
@have_which
end
# Execute a chunk of code.
def self.exec(code, interpreter = "/bin/sh")
raise ArgumentError, "non-sh interpreters are not currently supported" unless interpreter == "/bin/sh"
binary = code.split(/\s+/).shift
if have_which
path = nil
if binary !~ /^\//
path = %x{which #{binary} 2>/dev/null}.chomp
# we don't have the binary necessary
return nil if path == ""
else
path = binary
end
return nil unless FileTest.exists?(path)
end
out = nil
begin
out = %x{#{code}}.chomp
rescue => detail
$stderr.puts detail
return nil
end
if out == ""
return nil
else
return out
end
end
# Add a new confine to the resolution mechanism.
def confine(confines)
confines.each do |fact, values|
@confines.push Facter::Util::Confine.new(fact, *values)
end
end
# Create a new resolution mechanism.
def initialize(name)
@name = name
@confines = []
@value = nil
@timeout = 0
end
# Return the number of confines.
def length
@confines.length
end
# We need this as a getter for 'timeout', because some versions
# of ruby seem to already have a 'timeout' method and we can't
# seem to override the instance methods, somehow.
def limit
@timeout
end
# Set our code for returning a value.
def setcode(string = nil, interp = nil, &block)
if string
@code = string
@interpreter = interp || "/bin/sh"
else
unless block_given?
raise ArgumentError, "You must pass either code or a block"
end
@code = block
end
end
# Is this resolution mechanism suitable on the system in question?
def suitable?
unless defined? @suitable
@suitable = ! @confines.detect { |confine| ! confine.true? }
end
return @suitable
end
def to_s
return self.value()
end
# How we get a value for our resolution mechanism.
def value
result = nil
begin
Timeout.timeout(limit) do
if @code.is_a?(Proc)
result = @code.call()
else
result = Facter::Util::Resolution.exec(@code,@interpreter)
end
end
rescue Timeout::Error => detail
warn "Timed out seeking value for %s" % self.name
# This call avoids zombies -- basically, create a thread that will
# dezombify all of the child processes that we're ignoring because
# of the timeout.
Thread.new { Process.waitall }
return nil
end
return nil if result == ""
return result
end
end
|