Module: Puppet::Util
- Extended by:
- POSIX
- Included in:
- Puppet, Application, Application, Configurer, Configurer, Indirector::Queue, Interface, Network::AuthStore::Declaration, Network::Handler, Network::Handler::Master, Network::XMLRPCServer, Node::Exec, Parameter, Parameter, Parser::Compiler, Parser::Functions, Parser::Resource, Parser::Resource::Param, Parser::TemplateWrapper, Provider, Provider, Provider::Confine, Rails::Host, Resource::Catalog::Compiler, Transaction, Type, Type, Autoload, ClassGen, Diff, FileParsing, FileParsing::FileRecord, InstanceLoader, Instrumentation::Listener, Log, Log, ProviderFeatures::ProviderFeature, Reference, Storage
- Defined in:
- lib/vendor/puppet/util.rb,
lib/vendor/puppet/util/platform.rb,
lib/vendor/puppet/util/run_mode.rb,
lib/vendor/puppet/util/command_line.rb,
lib/vendor/puppet/util/execution_stub.rb
Defined Under Namespace
Modules: ADSI, Backups, CacheAccumulator, Cacher, Checksums, ClassGen, CollectionMerger, Colors, ConstantInflector, Diff, Docs, Errors, Execution, FileLocking, FileParsing, Graph, IniConfig, InlineDocs, InstanceLoader, Ldap, LogPaths, Logging, MethodHelper, NagiosMaker, POSIX, Package, Platform, ProviderFeatures, Pson, Queue, RDoc, ReferenceSerializer, RetryAction, SELinux, SUIDManager, SubclassLoader, SymbolicFileMode, Tagging, Terminal, Warnings, Windows Classes: Autoload, CommandLine, ExecutionStub, Feature, FileType, Instrumentation, LoadedFile, Log, Metric, NetworkDevice, Pidlock, Reference, ResourceTemplate, RunMode, Settings, Storage
Constant Summary collapse
- AbsolutePathWindows =
%r!^(?:(?:[A-Z]:#{slash})|(?:#{slash}#{slash}#{label}#{slash}#{label})|(?:#{slash}#{slash}\?#{slash}#{label}))!io
- AbsolutePathPosix =
%r!^/!
- @@sync_objects =
{}.extend MonitorMixin
Class Method Summary collapse
- .absolute_path?(path, platform = nil) ⇒ Boolean
- .activerecord_version ⇒ Object
- .benchmark(*args) ⇒ Object
-
.binread(file) ⇒ Object
Because IO#binread is only available in 1.9.
-
.chuser ⇒ Object
Change the process to a different user.
-
.classproxy(klass, objmethod, *methods) ⇒ Object
Proxy a bunch of methods to another object.
-
.execute(command, arguments = {:failonfail => true, :combine => true}) ⇒ Object
Execute the desired command, and return the status and output.
- .execute_posix(command, arguments, stdin, stdout, stderr) ⇒ Object
- .execute_windows(command, arguments, stdin, stdout, stderr) ⇒ Object
-
.logmethods(klass, useself = true) ⇒ Object
Create instance methods for each of the log levels.
- .memory ⇒ Object
-
.path_to_uri(path) ⇒ Object
Convert a path to a file URI.
-
.proxy(klass, objmethod, *methods) ⇒ Object
Proxy a bunch of methods to another object.
-
.replace_file(file, default_mode) {|tempfile| ... } ⇒ Object
Replace a file, securely.
- .safe_posix_fork(stdin = $stdin, stdout = $stdout, stderr = $stderr, &block) ⇒ Object
- .symbolize(value) ⇒ Object
- .symbolizehash(hash) ⇒ Object
- .symbolizehash!(hash) ⇒ Object
- .synchronize_on(x, type) ⇒ Object
-
.thinmark ⇒ Object
Just benchmark, with no logging.
-
.uri_to_path(uri) ⇒ Object
Get the path component of a URI.
- .wait_for_output(stdout) ⇒ Object
- .which(bin) ⇒ Object
-
.withumask(mask) ⇒ Object
Execute a given chunk of code with a new umask.
Instance Method Summary collapse
- #execfail(command, exception) ⇒ Object
-
#execpipe(command, failonfail = true) ⇒ Object
Execute the provided command with STDIN connected to a pipe, yielding the pipe object.
-
#threadlock(resource, type = Sync::EX) ⇒ Object
Create an exclusive lock.
Methods included from POSIX
get_posix_field, gid, idfield, methodbyid, methodbyname, search_posix_field, uid
Class Method Details
.absolute_path?(path, platform = nil) ⇒ Boolean
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/vendor/puppet/util.rb', line 204 def absolute_path?(path, platform=nil) # When running an internal subcommand (Application), the app requires puppet # which loads features, which creates an autoloader, which calls this method. # In that case, it isn't necessary to require puppet. When running an external # subcommand or if none was specified, then the CommandLine will call the # `which` method to resolve the external executable, and that requires features. # Rather then moving this require to handle the external subcommand case, or # no subcommand case, I'm undoing the performance change from 20efe94. This # code has been eliminated in 3.x since puppet can be required before loading # the application (since the default vardir/confdir locations are solely # based on user vs. system user, and not the application's run_mode). require 'puppet' # Ruby only sets File::ALT_SEPARATOR on Windows and the Ruby standard # library uses that to test what platform it's on. Normally in Puppet we # would use Puppet.features.microsoft_windows?, but this method needs to # be called during the initialization of features so it can't depend on # that. platform ||= Puppet::Util::Platform.windows? ? :windows : :posix regex = case platform when :windows AbsolutePathWindows when :posix AbsolutePathPosix else raise Puppet::DevError, "unknown platform #{platform} in absolute_path" end !! (path =~ regex) end |
.activerecord_version ⇒ Object
30 31 32 33 34 35 36 |
# File 'lib/vendor/puppet/util.rb', line 30 def self.activerecord_version if (defined?(::ActiveRecord) and defined?(::ActiveRecord::VERSION) and defined?(::ActiveRecord::VERSION::MAJOR) and defined?(::ActiveRecord::VERSION::MINOR)) ([::ActiveRecord::VERSION::MAJOR, ::ActiveRecord::VERSION::MINOR].join('.').to_f) else 0 end end |
.benchmark(*args) ⇒ Object
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/vendor/puppet/util.rb', line 136 def benchmark(*args) msg = args.pop level = args.pop object = nil if args.empty? if respond_to?(level) object = self else object = Puppet end else object = args.pop end raise Puppet::DevError, "Failed to provide level to :benchmark" unless level unless level == :none or object.respond_to? level raise Puppet::DevError, "Benchmarked object does not respond to #{level}" end # Only benchmark if our log level is high enough if level != :none and Puppet::Util::Log.sendlevel?(level) result = nil seconds = Benchmark.realtime { yield } object.send(level, msg + (" in %0.2f seconds" % seconds)) return seconds else yield end end |
.binread(file) ⇒ Object
Because IO#binread is only available in 1.9
518 519 520 |
# File 'lib/vendor/puppet/util.rb', line 518 def binread(file) File.open(file, 'rb') { |f| f.read } end |
.chuser ⇒ Object
Change the process to a different user
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/vendor/puppet/util.rb', line 53 def self.chuser if group = Puppet[:group] begin Puppet::Util::SUIDManager.change_group(group, true) rescue => detail Puppet.warning "could not change to group #{group.inspect}: #{detail}" $stderr.puts "could not change to group #{group.inspect}" # Don't exit on failed group changes, since it's # not fatal #exit(74) end end if user = Puppet[:user] begin Puppet::Util::SUIDManager.change_user(user, true) rescue => detail $stderr.puts "Could not change to user #{user}: #{detail}" exit(74) end end end |
.classproxy(klass, objmethod, *methods) ⇒ Object
Proxy a bunch of methods to another object.
103 104 105 106 107 108 109 110 111 112 |
# File 'lib/vendor/puppet/util.rb', line 103 def self.classproxy(klass, objmethod, *methods) classobj = class << klass; self; end methods.each do |method| classobj.send(:define_method, method) do |*args| obj = self.send(objmethod) obj.send(method, *args) end end end |
.execute(command, arguments = {:failonfail => true, :combine => true}) ⇒ Object
Execute the desired command, and return the status and output. def execute(command, failonfail = true, uid = nil, gid = nil) :combine sets whether or not to combine stdout/stderr in the output :stdinfile sets a file that can be used for stdin. Passing a string for stdin is not currently supported.
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 |
# File 'lib/vendor/puppet/util.rb', line 375 def execute(command, arguments = {:failonfail => true, :combine => true}) if command.is_a?(Array) command = command.flatten.map(&:to_s) str = command.join(" ") elsif command.is_a?(String) str = command end if respond_to? :debug debug "Executing '#{str}'" else Puppet.debug "Executing '#{str}'" end null_file = Puppet.features.microsoft_windows? ? 'NUL' : '/dev/null' stdin = File.open(arguments[:stdinfile] || null_file, 'r') stdout = arguments[:squelch] ? File.open(null_file, 'w') : Tempfile.new('puppet') stderr = arguments[:combine] ? stdout : File.open(null_file, 'w') exec_args = [command, arguments, stdin, stdout, stderr] if execution_stub = Puppet::Util::ExecutionStub.current_value return execution_stub.call(*exec_args) elsif Puppet.features.posix? child_pid = execute_posix(*exec_args) exit_status = Process.waitpid2(child_pid).last.exitstatus elsif Puppet.features.microsoft_windows? process_info = execute_windows(*exec_args) begin exit_status = Puppet::Util::Windows::Process.wait_process(process_info.process_handle) ensure Process.CloseHandle(process_info.process_handle) Process.CloseHandle(process_info.thread_handle) end end [stdin, stdout, stderr].each {|io| io.close rescue nil} # read output in if required unless arguments[:squelch] output = wait_for_output(stdout) Puppet.warning "Could not get output" unless output end if arguments[:failonfail] and exit_status != 0 raise ExecutionFailure, "Execution of '#{str}' returned #{exit_status}: #{output}" end output end |
.execute_posix(command, arguments, stdin, stdout, stderr) ⇒ Object
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 |
# File 'lib/vendor/puppet/util.rb', line 324 def execute_posix(command, arguments, stdin, stdout, stderr) child_pid = safe_posix_fork(stdin, stdout, stderr) do # We can't just call Array(command), and rely on it returning # things like ['foo'], when passed ['foo'], because # Array(command) will call command.to_a internally, which when # given a string can end up doing Very Bad Things(TM), such as # turning "/tmp/foo;\r\n /bin/echo" into ["/tmp/foo;\r\n", " /bin/echo"] command = [command].flatten Process.setsid begin Puppet::Util::SUIDManager.change_privileges(arguments[:uid], arguments[:gid], true) ENV['LANG'] = ENV['LC_ALL'] = ENV['LC_MESSAGES'] = ENV['LANGUAGE'] = 'C' Kernel.exec(*command) rescue => detail puts detail.to_s exit!(1) end end child_pid end |
.execute_windows(command, arguments, stdin, stdout, stderr) ⇒ Object
361 362 363 364 365 366 367 |
# File 'lib/vendor/puppet/util.rb', line 361 def execute_windows(command, arguments, stdin, stdout, stderr) command = command.map do |part| part.include?(' ') ? %Q["#{part.gsub(/"/, '\"')}"] : part end.join(" ") if command.is_a?(Array) Puppet::Util::Windows::Process.execute(command, arguments, stdin, stdout, stderr) end |
.logmethods(klass, useself = true) ⇒ Object
Create instance methods for each of the log levels. This allows the messages to be a little richer. Most classes will be calling this method.
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/vendor/puppet/util.rb', line 80 def self.logmethods(klass, useself = true) Puppet::Util::Log.eachlevel { |level| klass.send(:define_method, level, proc { |args| args = args.join(" ") if args.is_a?(Array) if useself Puppet::Util::Log.create( :level => level, :source => self, :message => args ) else Puppet::Util::Log.create( :level => level, :message => args ) end }) } end |
.memory ⇒ Object
462 463 464 465 466 467 468 469 470 471 |
# File 'lib/vendor/puppet/util.rb', line 462 def memory unless defined?(@pmap) @pmap = which('pmap') end if @pmap %x{#{@pmap} #{Process.pid}| grep total}.chomp.sub(/^\s*total\s+/, '').sub(/K$/, '').to_i else 0 end end |
.path_to_uri(path) ⇒ Object
Convert a path to a file URI
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/vendor/puppet/util.rb', line 237 def path_to_uri(path) return unless path params = { :scheme => 'file' } if Puppet.features.microsoft_windows? path = path.gsub(/\\/, '/') if unc = /^\/\/([^\/]+)(\/[^\/]+)/.match(path) params[:host] = unc[1] path = unc[2] elsif path =~ /^[a-z]:\//i path = '/' + path end end params[:path] = URI.escape(path) begin URI::Generic.build(params) rescue => detail raise Puppet::Error, "Failed to convert '#{path}' to URI: #{detail}" end end |
.proxy(klass, objmethod, *methods) ⇒ Object
Proxy a bunch of methods to another object.
115 116 117 118 119 120 121 122 123 |
# File 'lib/vendor/puppet/util.rb', line 115 def self.proxy(klass, objmethod, *methods) methods.each do |method| klass.send(:define_method, method) do |*args| obj = self.send(objmethod) obj.send(method, *args) end end end |
.replace_file(file, default_mode) {|tempfile| ... } ⇒ Object
Replace a file, securely. This takes a block, and passes it the file handle of a file open for writing. Write the replacement content inside the block and it will safely replace the target file.
This method will make no changes to the target file until the content is successfully written and the block returns without raising an error.
As far as possible the state of the existing file, such as mode, is preserved. This works hard to avoid loss of any metadata, but will result in an inode change for the file.
Arguments: ‘filename`, `default_mode`
The filename is the file we are going to replace.
The default_mode is the mode to use when the target file doesn’t already exist; if the file is present we copy the existing mode/owner/group values across.
541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 |
# File 'lib/vendor/puppet/util.rb', line 541 def replace_file(file, default_mode, &block) raise Puppet::DevError, "replace_file requires a block" unless block_given? file = Pathname(file) tempfile = Tempfile.new(file.basename.to_s, file.dirname.to_s) file_exists = file.exist? # Set properties of the temporary file before we write the content, because # Tempfile doesn't promise to be safe from reading by other people, just # that it avoids races around creating the file. # # Our Windows emulation is pretty limited, and so we have to carefully # and specifically handle the platform, which has all sorts of magic. # So, unlike Unix, we don't pre-prep security; we use the default "quite # secure" tempfile permissions instead. Magic happens later. unless Puppet.features.microsoft_windows? # Grab the current file mode, and fall back to the defaults. stat = file.lstat rescue OpenStruct.new(:mode => default_mode, :uid => Process.euid, :gid => Process.egid) # We only care about the bottom four slots, which make the real mode, # and not the rest of the platform stat call fluff and stuff. tempfile.chmod(stat.mode & 07777) tempfile.chown(stat.uid, stat.gid) end # OK, now allow the caller to write the content of the file. yield tempfile # Now, make sure the data (which includes the mode) is safe on disk. tempfile.flush begin tempfile.fsync rescue NotImplementedError # fsync may not be implemented by Ruby on all platforms, but # there is absolutely no recovery path if we detect that. So, we just # ignore the return code. # # However, don't be fooled: that is accepting that we are running in # an unsafe fashion. If you are porting to a new platform don't stub # that out. end tempfile.close if Puppet.features.microsoft_windows? # This will appropriately clone the file, but only if the file we are # replacing exists. Which is kind of annoying; thanks Microsoft. # # So, to avoid getting into an infinite loop we will retry once if the # file doesn't exist, but only the once... have_retried = false begin # Yes, the arguments are reversed compared to the rename in the rest # of the world. Puppet::Util::Windows::File.replace_file(file, tempfile.path) rescue Puppet::Util::Windows::Error => e # This might race, but there are enough possible cases that there # isn't a good, solid "better" way to do this, and the next call # should fail in the same way anyhow. raise if have_retried or File.exist?(file) have_retried = true # OK, so, we can't replace a file that doesn't exist, so let us put # one in place and set the permissions. Then we can retry and the # magic makes this all work. # # This is the least-worst option for handling Windows, as far as we # can determine. File.open(file, 'a') do |fh| # this space deliberately left empty for auto-close behaviour, # append mode, and not actually changing any of the content. end # Set the permissions to what we want. Puppet::Util::Windows::Security.set_mode(default_mode, file.to_s) # ...and finally retry the operation. retry end else File.rename(tempfile.path, file) end # Ideally, we would now fsync the directory as well, but Ruby doesn't # have support for that, and it doesn't matter /that/ much... # Return something true, and possibly useful. file end |
.safe_posix_fork(stdin = $stdin, stdout = $stdout, stderr = $stderr, &block) ⇒ Object
347 348 349 350 351 352 353 354 355 356 357 358 |
# File 'lib/vendor/puppet/util.rb', line 347 def safe_posix_fork(stdin=$stdin, stdout=$stdout, stderr=$stderr, &block) child_pid = Kernel.fork do $stdin.reopen(stdin) $stdout.reopen(stdout) $stderr.reopen(stderr) 3.upto(256){|fd| IO::new(fd).close rescue nil} block.call if block end child_pid end |
.symbolize(value) ⇒ Object
473 474 475 476 477 478 479 480 |
# File 'lib/vendor/puppet/util.rb', line 473 def symbolize(value) Puppet.deprecation_warning "symbolize is deprecated. Call the intern method on the object instead." if value.respond_to? :intern value.intern else value end end |
.symbolizehash(hash) ⇒ Object
482 483 484 485 486 487 488 489 490 491 492 |
# File 'lib/vendor/puppet/util.rb', line 482 def symbolizehash(hash) newhash = {} hash.each do |name, val| if name.is_a? String newhash[name.intern] = val else newhash[name] = val end end newhash end |
.symbolizehash!(hash) ⇒ Object
494 495 496 497 498 499 500 501 502 503 |
# File 'lib/vendor/puppet/util.rb', line 494 def symbolizehash!(hash) Puppet.deprecation_warning "symbolizehash! is deprecated. Use the non-destructive symbolizehash method instead." # this is not the most memory-friendly way to accomplish this, but the # code re-use and clarity seems worthwhile. newhash = symbolizehash(hash) hash.clear hash.merge!(newhash) hash end |
.synchronize_on(x, type) ⇒ Object
38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/vendor/puppet/util.rb', line 38 def self.synchronize_on(x,type) sync_object,users = 0,1 begin @@sync_objects.synchronize { (@@sync_objects[x] ||= [Sync.new,0])[users] += 1 } @@sync_objects[x][sync_object].synchronize(type) { yield } ensure @@sync_objects.synchronize { @@sync_objects.delete(x) unless (@@sync_objects[x][users] -= 1) > 0 } end end |
.thinmark ⇒ Object
Just benchmark, with no logging.
507 508 509 510 511 512 513 |
# File 'lib/vendor/puppet/util.rb', line 507 def thinmark seconds = Benchmark.realtime { yield } seconds end |
.uri_to_path(uri) ⇒ Object
Get the path component of a URI
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 |
# File 'lib/vendor/puppet/util.rb', line 264 def uri_to_path(uri) return unless uri.is_a?(URI) path = URI.unescape(uri.path) if Puppet.features.microsoft_windows? and uri.scheme == 'file' if uri.host path = "//#{uri.host}" + path # UNC else path.sub!(/^\//, '') end end path end |
.wait_for_output(stdout) ⇒ Object
429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 |
# File 'lib/vendor/puppet/util.rb', line 429 def wait_for_output(stdout) # Make sure the file's actually been written. This is basically a race # condition, and is probably a horrible way to handle it, but, well, oh # well. 2.times do |try| if File.exists?(stdout.path) stdout.open begin return stdout.read ensure stdout.close stdout.unlink end else time_to_sleep = try / 2.0 Puppet.warning "Waiting for output; will sleep #{time_to_sleep} seconds" sleep(time_to_sleep) end end nil end |
.which(bin) ⇒ Object
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
# File 'lib/vendor/puppet/util.rb', line 170 def which(bin) if absolute_path?(bin) return bin if FileTest.file? bin and FileTest.executable? bin else ENV['PATH'].split(File::PATH_SEPARATOR).each do |dir| begin dest = File.(File.join(dir, bin)) if Puppet.features.microsoft_windows? && File.extname(dest).empty? exts = ENV['PATHEXT'] exts = exts ? exts.split(File::PATH_SEPARATOR) : %w[.COM .EXE .BAT .CMD] exts.each do |ext| destext = File.(dest + ext) return destext if FileTest.file? destext and FileTest.executable? destext end end return dest if FileTest.file? dest and FileTest.executable? dest rescue ArgumentError => e raise unless e.to_s =~ /doesn't exist|can't find user/ # ...otherwise, we just skip the non-existent entry, and do nothing. end end end nil end |
Instance Method Details
#execfail(command, exception) ⇒ Object
317 318 319 320 321 322 |
# File 'lib/vendor/puppet/util.rb', line 317 def execfail(command, exception) output = execute(command) return output rescue ExecutionFailure raise exception, output end |
#execpipe(command, failonfail = true) ⇒ Object
Execute the provided command with STDIN connected to a pipe, yielding the pipe object. That allows data to be fed to that subprocess.
The command can be a simple string, which is executed as-is, or an Array, which is treated as a set of command arguments to pass through.#
In all cases this is passed directly to the shell, and STDOUT and STDERR are connected together during execution.
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 |
# File 'lib/vendor/puppet/util.rb', line 289 def execpipe(command, failonfail = true) if respond_to? :debug debug "Executing '#{command}'" else Puppet.debug "Executing '#{command}'" end # Paste together an array with spaces. We used to paste directly # together, no spaces, which made for odd invocations; the user had to # include whitespace between arguments. # # Having two spaces is really not a big drama, since this passes to the # shell anyhow, while no spaces makes for a small developer cost every # time this is invoked. --daniel 2012-02-13 command_str = command.respond_to?(:join) ? command.join(' ') : command output = open("| #{command_str} 2>&1") do |pipe| yield pipe end if failonfail unless $CHILD_STATUS == 0 raise ExecutionFailure, output end end output end |
#threadlock(resource, type = Sync::EX) ⇒ Object
Create an exclusive lock.
453 454 455 |
# File 'lib/vendor/puppet/util.rb', line 453 def threadlock(resource, type = Sync::EX) Puppet::Util.synchronize_on(resource,type) { yield } end |