Module: Puppet::Util

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

Instance Method Summary collapse

Methods included from POSIX

get_posix_field, gid, idfield, methodbyid, methodbyname, search_posix_field, uid

Class Method Details

.absolute_path?(path, platform = nil) ⇒ Boolean

Returns:

  • (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_versionObject



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

Raises:



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

.chuserObject

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

.memoryObject



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.

Yields:

  • (tempfile)

Raises:



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

.thinmarkObject

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.expand_path(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.expand_path(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

.withumask(mask) ⇒ Object

Execute a given chunk of code with a new umask.



126
127
128
129
130
131
132
133
134
# File 'lib/vendor/puppet/util.rb', line 126

def self.withumask(mask)
  cur = File.umask(mask)

  begin
    yield
  ensure
    File.umask(cur)
  end
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