choria-mcorpc-support 0.0.1

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 (133) hide show
  1. checksums.yaml +7 -0
  2. data/bin/mco +64 -0
  3. data/lib/mcollective.rb +63 -0
  4. data/lib/mcollective/agent.rb +5 -0
  5. data/lib/mcollective/agents.rb +149 -0
  6. data/lib/mcollective/aggregate.rb +85 -0
  7. data/lib/mcollective/aggregate/average.ddl +33 -0
  8. data/lib/mcollective/aggregate/average.rb +29 -0
  9. data/lib/mcollective/aggregate/base.rb +40 -0
  10. data/lib/mcollective/aggregate/result.rb +9 -0
  11. data/lib/mcollective/aggregate/result/base.rb +25 -0
  12. data/lib/mcollective/aggregate/result/collection_result.rb +19 -0
  13. data/lib/mcollective/aggregate/result/numeric_result.rb +13 -0
  14. data/lib/mcollective/aggregate/sum.ddl +33 -0
  15. data/lib/mcollective/aggregate/sum.rb +18 -0
  16. data/lib/mcollective/aggregate/summary.ddl +33 -0
  17. data/lib/mcollective/aggregate/summary.rb +53 -0
  18. data/lib/mcollective/application.rb +365 -0
  19. data/lib/mcollective/application/completion.rb +104 -0
  20. data/lib/mcollective/application/describe_filter.rb +87 -0
  21. data/lib/mcollective/application/facts.rb +62 -0
  22. data/lib/mcollective/application/find.rb +23 -0
  23. data/lib/mcollective/application/help.rb +28 -0
  24. data/lib/mcollective/application/inventory.rb +344 -0
  25. data/lib/mcollective/application/ping.rb +82 -0
  26. data/lib/mcollective/application/plugin.rb +369 -0
  27. data/lib/mcollective/application/rpc.rb +111 -0
  28. data/lib/mcollective/applications.rb +134 -0
  29. data/lib/mcollective/cache.rb +145 -0
  30. data/lib/mcollective/client.rb +353 -0
  31. data/lib/mcollective/config.rb +245 -0
  32. data/lib/mcollective/connector.rb +18 -0
  33. data/lib/mcollective/connector/base.rb +26 -0
  34. data/lib/mcollective/data.rb +91 -0
  35. data/lib/mcollective/data/agent_data.ddl +22 -0
  36. data/lib/mcollective/data/agent_data.rb +17 -0
  37. data/lib/mcollective/data/base.rb +67 -0
  38. data/lib/mcollective/data/collective_data.ddl +20 -0
  39. data/lib/mcollective/data/collective_data.rb +9 -0
  40. data/lib/mcollective/data/fact_data.ddl +28 -0
  41. data/lib/mcollective/data/fact_data.rb +55 -0
  42. data/lib/mcollective/data/fstat_data.ddl +89 -0
  43. data/lib/mcollective/data/fstat_data.rb +56 -0
  44. data/lib/mcollective/data/result.rb +45 -0
  45. data/lib/mcollective/ddl.rb +113 -0
  46. data/lib/mcollective/ddl/agentddl.rb +253 -0
  47. data/lib/mcollective/ddl/base.rb +217 -0
  48. data/lib/mcollective/ddl/dataddl.rb +56 -0
  49. data/lib/mcollective/ddl/discoveryddl.rb +52 -0
  50. data/lib/mcollective/ddl/validatorddl.rb +6 -0
  51. data/lib/mcollective/discovery.rb +143 -0
  52. data/lib/mcollective/discovery/flatfile.ddl +11 -0
  53. data/lib/mcollective/discovery/flatfile.rb +48 -0
  54. data/lib/mcollective/discovery/mc.ddl +11 -0
  55. data/lib/mcollective/discovery/mc.rb +30 -0
  56. data/lib/mcollective/discovery/stdin.ddl +11 -0
  57. data/lib/mcollective/discovery/stdin.rb +68 -0
  58. data/lib/mcollective/exceptions.rb +28 -0
  59. data/lib/mcollective/facts.rb +39 -0
  60. data/lib/mcollective/facts/base.rb +100 -0
  61. data/lib/mcollective/facts/yaml_facts.rb +65 -0
  62. data/lib/mcollective/generators.rb +7 -0
  63. data/lib/mcollective/generators/agent_generator.rb +51 -0
  64. data/lib/mcollective/generators/base.rb +46 -0
  65. data/lib/mcollective/generators/data_generator.rb +51 -0
  66. data/lib/mcollective/generators/templates/action_snippet.erb +13 -0
  67. data/lib/mcollective/generators/templates/data_input_snippet.erb +7 -0
  68. data/lib/mcollective/generators/templates/ddl.erb +8 -0
  69. data/lib/mcollective/generators/templates/plugin.erb +7 -0
  70. data/lib/mcollective/log.rb +118 -0
  71. data/lib/mcollective/logger.rb +5 -0
  72. data/lib/mcollective/logger/base.rb +77 -0
  73. data/lib/mcollective/logger/console_logger.rb +61 -0
  74. data/lib/mcollective/logger/file_logger.rb +53 -0
  75. data/lib/mcollective/logger/syslog_logger.rb +53 -0
  76. data/lib/mcollective/matcher.rb +224 -0
  77. data/lib/mcollective/matcher/parser.rb +128 -0
  78. data/lib/mcollective/matcher/scanner.rb +241 -0
  79. data/lib/mcollective/message.rb +248 -0
  80. data/lib/mcollective/monkey_patches.rb +152 -0
  81. data/lib/mcollective/optionparser.rb +197 -0
  82. data/lib/mcollective/pluginmanager.rb +180 -0
  83. data/lib/mcollective/pluginpackager.rb +98 -0
  84. data/lib/mcollective/pluginpackager/agent_definition.rb +94 -0
  85. data/lib/mcollective/pluginpackager/debpackage_packager.rb +237 -0
  86. data/lib/mcollective/pluginpackager/modulepackage_packager.rb +127 -0
  87. data/lib/mcollective/pluginpackager/ospackage_packager.rb +59 -0
  88. data/lib/mcollective/pluginpackager/rpmpackage_packager.rb +180 -0
  89. data/lib/mcollective/pluginpackager/standard_definition.rb +69 -0
  90. data/lib/mcollective/pluginpackager/templates/debian/Makefile.erb +7 -0
  91. data/lib/mcollective/pluginpackager/templates/debian/changelog.erb +5 -0
  92. data/lib/mcollective/pluginpackager/templates/debian/compat.erb +1 -0
  93. data/lib/mcollective/pluginpackager/templates/debian/control.erb +15 -0
  94. data/lib/mcollective/pluginpackager/templates/debian/copyright.erb +8 -0
  95. data/lib/mcollective/pluginpackager/templates/debian/rules.erb +6 -0
  96. data/lib/mcollective/pluginpackager/templates/module/Modulefile.erb +5 -0
  97. data/lib/mcollective/pluginpackager/templates/module/README.md.erb +37 -0
  98. data/lib/mcollective/pluginpackager/templates/module/_manifest.pp.erb +9 -0
  99. data/lib/mcollective/pluginpackager/templates/redhat/rpm_spec.erb +63 -0
  100. data/lib/mcollective/registration/base.rb +91 -0
  101. data/lib/mcollective/rpc.rb +182 -0
  102. data/lib/mcollective/rpc/actionrunner.rb +158 -0
  103. data/lib/mcollective/rpc/agent.rb +374 -0
  104. data/lib/mcollective/rpc/audit.rb +38 -0
  105. data/lib/mcollective/rpc/client.rb +1066 -0
  106. data/lib/mcollective/rpc/helpers.rb +321 -0
  107. data/lib/mcollective/rpc/progress.rb +63 -0
  108. data/lib/mcollective/rpc/reply.rb +87 -0
  109. data/lib/mcollective/rpc/request.rb +86 -0
  110. data/lib/mcollective/rpc/result.rb +90 -0
  111. data/lib/mcollective/rpc/stats.rb +294 -0
  112. data/lib/mcollective/runnerstats.rb +90 -0
  113. data/lib/mcollective/security.rb +26 -0
  114. data/lib/mcollective/security/base.rb +244 -0
  115. data/lib/mcollective/shell.rb +126 -0
  116. data/lib/mcollective/ssl.rb +285 -0
  117. data/lib/mcollective/util.rb +579 -0
  118. data/lib/mcollective/validator.rb +85 -0
  119. data/lib/mcollective/validator/array_validator.ddl +7 -0
  120. data/lib/mcollective/validator/array_validator.rb +9 -0
  121. data/lib/mcollective/validator/ipv4address_validator.ddl +7 -0
  122. data/lib/mcollective/validator/ipv4address_validator.rb +16 -0
  123. data/lib/mcollective/validator/ipv6address_validator.ddl +7 -0
  124. data/lib/mcollective/validator/ipv6address_validator.rb +16 -0
  125. data/lib/mcollective/validator/length_validator.ddl +7 -0
  126. data/lib/mcollective/validator/length_validator.rb +11 -0
  127. data/lib/mcollective/validator/regex_validator.ddl +7 -0
  128. data/lib/mcollective/validator/regex_validator.rb +9 -0
  129. data/lib/mcollective/validator/shellsafe_validator.ddl +7 -0
  130. data/lib/mcollective/validator/shellsafe_validator.rb +13 -0
  131. data/lib/mcollective/validator/typecheck_validator.ddl +7 -0
  132. data/lib/mcollective/validator/typecheck_validator.rb +28 -0
  133. metadata +215 -0
@@ -0,0 +1,90 @@
1
+ module MCollective
2
+ # Class to store stats about the mcollectived, it should live in the PluginManager
3
+ # so that agents etc can get hold of it and return the stats to callers
4
+ class RunnerStats
5
+ def initialize
6
+ @starttime = Time.now.to_i
7
+ @validated = 0
8
+ @unvalidated = 0
9
+ @filtered = 0
10
+ @passed = 0
11
+ @total = 0
12
+ @replies = 0
13
+ @ttlexpired = 0
14
+
15
+ @mutex = Mutex.new
16
+ end
17
+
18
+ # Records a message that failed TTL checks
19
+ def ttlexpired
20
+ Log.debug("Incrementing ttl expired stat")
21
+ @ttlexpired += 1
22
+ end
23
+
24
+ # Records a message that passed the filters
25
+ def passed
26
+ Log.debug("Incrementing passed stat")
27
+ @passed += 1
28
+ end
29
+
30
+ # Records a message that didnt pass the filters
31
+ def filtered
32
+ Log.debug("Incrementing filtered stat")
33
+ @filtered += 1
34
+ end
35
+
36
+ # Records a message that validated ok
37
+ def validated
38
+ Log.debug("Incrementing validated stat")
39
+ @validated += 1
40
+ end
41
+
42
+ def unvalidated
43
+ Log.debug("Incrementing unvalidated stat")
44
+ @unvalidated += 1
45
+ end
46
+
47
+ # Records receipt of a message
48
+ def received
49
+ Log.debug("Incrementing total stat")
50
+ @total += 1
51
+ end
52
+
53
+ # Records sending a message
54
+ def sent
55
+ @mutex.synchronize do
56
+ Log.debug("Incrementing replies stat")
57
+ @replies += 1
58
+ end
59
+ end
60
+
61
+ # Returns a hash with all stats
62
+ def to_hash
63
+ stats = {:validated => @validated,
64
+ :unvalidated => @unvalidated,
65
+ :passed => @passed,
66
+ :filtered => @filtered,
67
+ :starttime => @starttime,
68
+ :total => @total,
69
+ :ttlexpired => @ttlexpired,
70
+ :replies => @replies}
71
+
72
+ reply = {:stats => stats,
73
+ :threads => [],
74
+ :pid => Process.pid,
75
+ :times => {} }
76
+
77
+ ::Process.times.each_pair{|k,v|
78
+ k = k.to_sym
79
+ reply[:times][k] = v
80
+ }
81
+
82
+ Thread.list.each do |t|
83
+ reply[:threads] << "#{t.inspect}"
84
+ end
85
+
86
+ reply[:agents] = Agents.agentlist
87
+ reply
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,26 @@
1
+ module MCollective
2
+ # Security is implimented using a module structure and installations
3
+ # can configure which module they want to use.
4
+ #
5
+ # Security modules deal with various aspects of authentication and authorization:
6
+ #
7
+ # - Determines if a filter excludes this host from dealing with a request
8
+ # - Serialization and Deserialization of messages
9
+ # - Validation of messages against keys, certificates or whatever the class choose to impliment
10
+ # - Encoding and Decoding of messages
11
+ #
12
+ # To impliment a new security class using SSL for example you would inherit from the base
13
+ # class and only impliment:
14
+ #
15
+ # - decodemsg
16
+ # - encodereply
17
+ # - encoderequest
18
+ # - validrequest?
19
+ #
20
+ # Each of these methods should increment various stats counters, see the default MCollective::Security::Psk module for examples of this
21
+ #
22
+ # Filtering can be extended by providing a new validate_filter? method.
23
+ module Security
24
+ require "mcollective/security/base"
25
+ end
26
+ end
@@ -0,0 +1,244 @@
1
+ module MCollective
2
+ module Security
3
+ # This is a base class the other security modules should inherit from
4
+ # it handles statistics and validation of messages that should in most
5
+ # cases apply to all security models.
6
+ #
7
+ # To create your own security plugin you should provide a plugin that inherits
8
+ # from this and provides the following methods:
9
+ #
10
+ # decodemsg - Decodes a message that was received from the middleware
11
+ # encodereply - Encodes a reply message to a previous request message
12
+ # encoderequest - Encodes a new request message
13
+ # validrequest? - Validates a request received from the middleware
14
+ #
15
+ # Optionally if you are identifying users by some other means like certificate name
16
+ # you can provide your own callerid method that can provide the rest of the system
17
+ # with an id, and you would see this id being usable in SimpleRPC authorization methods
18
+ #
19
+ # The @initiated_by variable will be set to either :client or :node depending on
20
+ # who is using this plugin. This is to help security providers that operate in an
21
+ # asymetric mode like public/private key based systems.
22
+ #
23
+ # Specifics of each of these are a bit fluid and the interfaces for this is not
24
+ # set in stone yet, specifically the encode methods will be provided with a helper
25
+ # that takes care of encoding the core requirements. The best place to see how security
26
+ # works is by looking at the provided MCollective::Security::PSK plugin.
27
+ class Base
28
+ attr_reader :stats
29
+ attr_accessor :initiated_by
30
+
31
+ # Register plugins that inherits base
32
+ def self.inherited(klass)
33
+ PluginManager << {:type => "security_plugin", :class => klass.to_s}
34
+ end
35
+
36
+ # Initializes configuration and logging as well as prepare a zero'd hash of stats
37
+ # various security methods and filter validators should increment stats, see MCollective::Security::Psk for a sample
38
+ def initialize
39
+ @config = Config.instance
40
+ @log = Log
41
+ @stats = PluginManager["global_stats"]
42
+ end
43
+
44
+ # Takes a Hash with a filter in it and validates it against host information.
45
+ #
46
+ # At present this supports filter matches against the following criteria:
47
+ #
48
+ # - puppet_class|cf_class - Presence of a configuration management class in
49
+ # the file configured with classesfile
50
+ # - agent - Presence of a MCollective agent with a supplied name
51
+ # - fact - The value of a fact avout this system
52
+ # - identity - the configured identity of the system
53
+ #
54
+ # TODO: Support REGEX and/or multiple filter keys to be AND'd
55
+ def validate_filter?(filter)
56
+ failed = 0
57
+ passed = 0
58
+
59
+ passed = 1 if Util.empty_filter?(filter)
60
+
61
+ filter.keys.each do |key|
62
+ case key
63
+ when /puppet_class|cf_class/
64
+ filter[key].each do |f|
65
+ Log.debug("Checking for class #{f}")
66
+ if Util.has_cf_class?(f) then
67
+ Log.debug("Passing based on configuration management class #{f}")
68
+ passed += 1
69
+ else
70
+ Log.debug("Failing based on configuration management class #{f}")
71
+ failed += 1
72
+ end
73
+ end
74
+
75
+ when "compound"
76
+ filter[key].each do |compound|
77
+ result = false
78
+ truth_values = []
79
+
80
+ begin
81
+ compound.each do |expression|
82
+ case expression.keys.first
83
+ when "statement"
84
+ truth_values << Matcher.eval_compound_statement(expression).to_s
85
+ when "fstatement"
86
+ truth_values << Matcher.eval_compound_fstatement(expression.values.first)
87
+ when "and"
88
+ truth_values << "&&"
89
+ when "or"
90
+ truth_values << "||"
91
+ when "("
92
+ truth_values << "("
93
+ when ")"
94
+ truth_values << ")"
95
+ when "not"
96
+ truth_values << "!"
97
+ end
98
+ end
99
+
100
+ result = eval(truth_values.join(" "))
101
+ rescue DDLValidationError
102
+ result = false
103
+ end
104
+
105
+ if result
106
+ Log.debug("Passing based on class and fact composition")
107
+ passed +=1
108
+ else
109
+ Log.debug("Failing based on class and fact composition")
110
+ failed +=1
111
+ end
112
+ end
113
+
114
+ when "agent"
115
+ filter[key].each do |f|
116
+ if Util.has_agent?(f) || f == "mcollective"
117
+ Log.debug("Passing based on agent #{f}")
118
+ passed += 1
119
+ else
120
+ Log.debug("Failing based on agent #{f}")
121
+ failed += 1
122
+ end
123
+ end
124
+
125
+ when "fact"
126
+ filter[key].each do |f|
127
+ if Util.has_fact?(f[:fact], f[:value], f[:operator])
128
+ Log.debug("Passing based on fact #{f[:fact]} #{f[:operator]} #{f[:value]}")
129
+ passed += 1
130
+ else
131
+ Log.debug("Failing based on fact #{f[:fact]} #{f[:operator]} #{f[:value]}")
132
+ failed += 1
133
+ end
134
+ end
135
+
136
+ when "identity"
137
+ unless filter[key].empty?
138
+ # Identity filters should not be 'and' but 'or' as each node can only have one identity
139
+ matched = filter[key].select{|f| Util.has_identity?(f)}.size
140
+
141
+ if matched == 1
142
+ Log.debug("Passing based on identity")
143
+ passed += 1
144
+ else
145
+ Log.debug("Failed based on identity")
146
+ failed += 1
147
+ end
148
+ end
149
+ end
150
+ end
151
+
152
+ if failed == 0 && passed > 0
153
+ Log.debug("Message passed the filter checks")
154
+
155
+ @stats.passed
156
+
157
+ return true
158
+ else
159
+ Log.debug("Message failed the filter checks")
160
+
161
+ @stats.filtered
162
+
163
+ return false
164
+ end
165
+ end
166
+
167
+ def create_reply(reqid, agent, body)
168
+ Log.debug("Encoded a message for request #{reqid}")
169
+
170
+ {:senderid => @config.identity,
171
+ :requestid => reqid,
172
+ :senderagent => agent,
173
+ :msgtime => Time.now.utc.to_i,
174
+ :body => body}
175
+ end
176
+
177
+ def create_request(reqid, filter, msg, initiated_by, target_agent, target_collective, ttl=60)
178
+ Log.debug("Encoding a request for agent '#{target_agent}' in collective #{target_collective} with request id #{reqid}")
179
+
180
+ {:body => msg,
181
+ :senderid => @config.identity,
182
+ :requestid => reqid,
183
+ :filter => filter,
184
+ :collective => target_collective,
185
+ :agent => target_agent,
186
+ :callerid => callerid,
187
+ :ttl => ttl,
188
+ :msgtime => Time.now.utc.to_i}
189
+ end
190
+
191
+ # Give a MC::Message instance and a message id this will figure out if you the incoming
192
+ # message id matches the one the Message object is expecting and raise if its not
193
+ #
194
+ # Mostly used by security plugins to figure out if they should do the hard work of decrypting
195
+ # etc messages that would only later on be ignored
196
+ def should_process_msg?(msg, msgid)
197
+ if msg.expected_msgid
198
+ unless msg.expected_msgid == msgid
199
+ msgtext = "Got a message with id %s but was expecting %s, ignoring message" % [msgid, msg.expected_msgid]
200
+ Log.debug msgtext
201
+ raise MsgDoesNotMatchRequestID, msgtext
202
+ end
203
+ end
204
+
205
+ true
206
+ end
207
+
208
+ # Validates a callerid. We do not want to allow things like \ and / in
209
+ # callerids since other plugins make assumptions that these are safe strings.
210
+ #
211
+ # callerids are generally in the form uid=123 or cert=foo etc so we do that
212
+ # here but security plugins could override this for some complex uses
213
+ def valid_callerid?(id)
214
+ !!id.match(/^[\w]+=[\w\.\-]+$/)
215
+ end
216
+
217
+ # Returns a unique id for the caller, by default we just use the unix
218
+ # user id, security plugins can provide their own means of doing ids.
219
+ def callerid
220
+ "uid=#{Process.uid}"
221
+ end
222
+
223
+ # Security providers should provide this, see MCollective::Security::Psk
224
+ def validrequest?(req)
225
+ Log.error("validrequest? is not implemented in #{self.class}")
226
+ end
227
+
228
+ # Security providers should provide this, see MCollective::Security::Psk
229
+ def encoderequest(sender, msg, filter={})
230
+ Log.error("encoderequest is not implemented in #{self.class}")
231
+ end
232
+
233
+ # Security providers should provide this, see MCollective::Security::Psk
234
+ def encodereply(sender, msg, requestcallerid=nil)
235
+ Log.error("encodereply is not implemented in #{self.class}")
236
+ end
237
+
238
+ # Security providers should provide this, see MCollective::Security::Psk
239
+ def decodemsg(msg)
240
+ Log.error("decodemsg is not implemented in #{self.class}")
241
+ end
242
+ end
243
+ end
244
+ end
@@ -0,0 +1,126 @@
1
+ module MCollective
2
+ # Wrapper around systemu that handles executing of system commands
3
+ # in a way that makes stdout, stderr and status available. Supports
4
+ # timeouts and sets a default sane environment.
5
+ #
6
+ # s = Shell.new("date", opts)
7
+ # s.runcommand
8
+ # puts s.stdout
9
+ # puts s.stderr
10
+ # puts s.status.exitstatus
11
+ #
12
+ # Options hash can have:
13
+ #
14
+ # cwd - the working directory the command will be run from
15
+ # stdin - a string that will be sent to stdin of the program
16
+ # stdout - a variable that will receive stdout, must support <<
17
+ # stderr - a variable that will receive stdin, must support <<
18
+ # environment - the shell environment, defaults to include LC_ALL=C
19
+ # set to nil to clear the environment even of LC_ALL
20
+ # timeout - a timeout in seconds after which the subprocess is killed,
21
+ # the special value :on_thread_exit kills the subprocess
22
+ # when the invoking thread (typically the agent) has ended
23
+ #
24
+ class Shell
25
+ attr_reader :environment, :command, :status, :stdout, :stderr, :stdin, :cwd, :timeout
26
+
27
+ def initialize(command, options={})
28
+ @environment = {"LC_ALL" => "C"}
29
+ @command = command
30
+ @status = nil
31
+ @stdout = ""
32
+ @stderr = ""
33
+ @stdin = nil
34
+ @cwd = Dir.tmpdir
35
+ @timeout = nil
36
+
37
+ options.each do |opt, val|
38
+ case opt.to_s
39
+ when "stdout"
40
+ raise "stdout should support <<" unless val.respond_to?("<<")
41
+ @stdout = val
42
+
43
+ when "stderr"
44
+ raise "stderr should support <<" unless val.respond_to?("<<")
45
+ @stderr = val
46
+
47
+ when "stdin"
48
+ raise "stdin should be a String" unless val.is_a?(String)
49
+ @stdin = val
50
+
51
+ when "cwd"
52
+ raise "Directory #{val} does not exist" unless File.directory?(val)
53
+ @cwd = val
54
+
55
+ when "environment"
56
+ if val.nil?
57
+ @environment = {}
58
+ else
59
+ @environment.merge!(val.dup)
60
+ @environment = @environment.delete_if { |k,v| v.nil? }
61
+ end
62
+
63
+ when "timeout"
64
+ raise "timeout should be a positive integer or the symbol :on_thread_exit symbol" unless val.eql?(:on_thread_exit) || ( val.is_a?(Integer) && val>0 )
65
+ @timeout = val
66
+ end
67
+ end
68
+ end
69
+
70
+ # Actually does the systemu call passing in the correct environment, stdout and stderr
71
+ def runcommand
72
+ opts = {"env" => @environment,
73
+ "stdout" => @stdout,
74
+ "stderr" => @stderr,
75
+ "cwd" => @cwd}
76
+
77
+ opts["stdin"] = @stdin if @stdin
78
+
79
+
80
+ thread = Thread.current
81
+ # Start a double fork and exec with systemu which implies a guard thread.
82
+ # If a valid timeout is configured the guard thread will terminate the
83
+ # executing process and reap the pid.
84
+ # If no timeout is specified the process will run to completion with the
85
+ # guard thread reaping the pid on completion.
86
+ @status = systemu(@command, opts) do |cid|
87
+ begin
88
+ if timeout.is_a?(Integer)
89
+ # wait for the specified timeout
90
+ sleep timeout
91
+ else
92
+ # sleep while the agent thread is still alive
93
+ while(thread.alive?)
94
+ sleep 0.1
95
+ end
96
+ end
97
+
98
+ # if the process is still running
99
+ if (Process.kill(0, cid))
100
+ # and a timeout was specified
101
+ if timeout
102
+ if Util.windows?
103
+ Process.kill('KILL', cid)
104
+ else
105
+ # Kill the process
106
+ Process.kill('TERM', cid)
107
+ sleep 2
108
+ Process.kill('KILL', cid) if (Process.kill(0, cid))
109
+ end
110
+ end
111
+ # only wait if the parent thread is dead
112
+ Process.waitpid(cid) unless thread.alive?
113
+ end
114
+ rescue SystemExit
115
+ rescue Errno::ESRCH
116
+ rescue Errno::ECHILD
117
+ Log.warn("Could not reap process '#{cid}'.")
118
+ rescue Exception => e
119
+ Log.info("Unexpected exception received while waiting for child process: #{e.class}: #{e}")
120
+ end
121
+ end
122
+ @status.thread.kill
123
+ @status
124
+ end
125
+ end
126
+ end