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.

Reply via email to