This replaces the implementation of atomic file replacement in the file type
with the replace_file API from Puppet::Util.
As part of this we extend the replace_file API to take a third parameter, a
lambda that is used to validate the temporary file after it is written and
flushed to disk, but before it is actually installed.
---
lib/puppet/type/file.rb | 50 ++++++++++++++++------------------------------
lib/puppet/util.rb | 28 ++++++++++++++++++++++++-
2 files changed, 44 insertions(+), 34 deletions(-)
diff --git a/lib/puppet/type/file.rb b/lib/puppet/type/file.rb
index fdb5a35..8641e2c 100644
--- a/lib/puppet/type/file.rb
+++ b/lib/puppet/type/file.rb
@@ -714,44 +714,30 @@ module Puppet
# Write out the file. Requires the content to be written,
# the property name for logging, and the checksum for validation.
def write(content, property, checksum = nil)
- if validate = validate_checksum?
+ remove_existing(:file)
+
+ # Extract a suitable mode for the file, defaulting to fairly open.
+ mode = self.should(:mode) || 0664
+
+ if validate_checksum?
# Use the appropriate checksum type -- md5, md5lite, etc.
sumtype = property(:checksum).checktype
checksum ||= "{#{sumtype}}" +
property(:checksum).send(sumtype, content)
- end
-
- remove_existing(:file)
- use_temporary_file = (content.length != 0)
- if use_temporary_file
- path = "#{self[:path]}.puppettmp_#{rand(10000)}"
- while File.exists?(path) or File.symlink?(path)
- path = "#{self[:path]}.puppettmp_#{rand(10000)}"
- end
- else
- path = self[:path]
- end
-
- mode = self.should(:mode) # might be nil
- umask = mode ? 000 : 022
-
- Puppet::Util.withumask(umask) do
- File.open(path, File::CREAT|File::WRONLY|File::TRUNC, mode) {
|f| f.print content }
- end
-
- # And put our new file in place
- if use_temporary_file # This is only not true when our file is
empty.
- begin
- fail_if_checksum_is_wrong(path, checksum) if validate
- File.rename(path, self[:path])
- rescue => detail
- fail "Could not rename temporary file %s to %s : %s" %
[path, self[:path], detail]
- ensure
- # Make sure the created file gets removed
- File.unlink(path) if FileTest.exists?(path)
- end
+ # a lambda used to validate the file before install...
+ validate = lambda {
+ |file| fail_if_checksum_is_wrong(file.path, checksum)
+ }
+ else
+ validate = nil
end
+ # Actually replace the file. Only done if we don't die in
validate.
+ Puppet::Util.replace_file(self[:path], mode, validate) {
+ |file|
+ file.print content
+ }
+
# make sure all of the modes are actually correct
property_fix
diff --git a/lib/puppet/util.rb b/lib/puppet/util.rb
index 45f4284..036032e 100644
--- a/lib/puppet/util.rb
+++ b/lib/puppet/util.rb
@@ -446,14 +446,19 @@ module Util
end
module_function :secure_open
- def replace_file (file, mode = 0600, &block)
+ def replace_file (file, mode = 0600, validate = nil, &block)
raise Puppet::DevError,"replace_file requires a block" unless
block_given?
+ if validate and !validate.is_a?(Lambda)
+ raise Puppet::DevError, "validate is not a lambda, but %s" %
validate.class
+ end
+
# Theoretically we should have an absolute path anyway, but let us be
# forgiving to the developers, shall we?
dir = File.dirname(File.expand_path(file))
tempfile = Tempfile.new(file, dir)
- # OK, now allow the caller to write the content of the file...
+ # OK, now allow the caller to write the content of the file, which
+ # happens in the context of our block.
yield tempfile
# Adjust the mode of the file on disk before we replace the old file,
@@ -472,6 +477,25 @@ module Util
tempfile.flush
tempfile.fsync
+ # Before we replace the file, but after the data is safely on disk, we
+ # let the caller validate the content. Maybe they need a file on disk
+ # to run an external validation tool over or something.
+ #
+ # We only give them a read-only handle to the file, though, to make it
+ # harder for a caller to do something silly in validate and abuse the
+ # trust we place in them.
+ if validate
+ # if validate throws an exception we clean up automatically anyhow.
+ result = validate.call(File.new(tempfile.path))
+
+ # ...and if it just returns false, fail anyway.
+ unless result
+ raise Puppet::Error,
+ "validation of scratch file %s failed while replacing %s" %
+ [tempfile.path, file]
+ end
+ end
+
# ...and then actually replace the original file. this *must* occur
# before tempfile is closed, or the class will delete the data on disk.
File.rename(tempfile.path, file)
--
1.7.0.4
--
You received this message because you are subscribed to the Google Groups
"Puppet Developers" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/puppet-dev?hl=en.