lib/puppet/util.rb in puppet-2.7.18 vs lib/puppet/util.rb in puppet-2.7.19
- old
+ new
@@ -7,10 +7,11 @@
require 'uri'
require 'sync'
require 'monitor'
require 'tempfile'
require 'pathname'
+require 'ostruct'
require 'puppet/util/platform'
module Puppet
# A command failed to execute.
require 'puppet/error'
@@ -415,15 +416,17 @@
# 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)
- output = stdout.open.read
-
- stdout.close(true)
-
- return output
+ 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
@@ -519,39 +522,36 @@
# 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.
def replace_file(file, default_mode, &block)
raise Puppet::DevError, "replace_file requires a block" unless block_given?
- raise Puppet::DevError, "replace_file is non-functional on Windows" if Puppet.features.microsoft_windows?
file = Pathname(file)
tempfile = Tempfile.new(file.basename.to_s, file.dirname.to_s)
file_exists = file.exist?
- # If the file exists, use its current mode/owner/group. If it doesn't, use
- # the supplied mode, and default to current user/group.
- if file_exists
- stat = file.lstat
-
- # We only care about the four lowest-order octets. Higher octets are
- # filesystem-specific.
- mode = stat.mode & 07777
- uid = stat.uid
- gid = stat.gid
- else
- mode = default_mode
- uid = Process.euid
- gid = Process.egid
- end
-
# 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.
- tempfile.chmod(mode)
- tempfile.chown(uid, gid)
+ #
+ # 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
@@ -567,10 +567,48 @@
# that out.
end
tempfile.close
- File.rename(tempfile.path, file)
+ 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.