summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/puppet/type/file.rb101
-rwxr-xr-xlib/puppet/type/file/checksum.rb487
-rwxr-xr-xlib/puppet/type/file/content.rb7
-rwxr-xr-xlib/puppet/type/file/ensure.rb14
-rwxr-xr-xlib/puppet/type/file/source.rb6
-rw-r--r--lib/puppet/util/checksums.rb2
6 files changed, 276 insertions, 341 deletions
diff --git a/lib/puppet/type/file.rb b/lib/puppet/type/file.rb
index 0ec54d907..f093543d7 100644
--- a/lib/puppet/type/file.rb
+++ b/lib/puppet/type/file.rb
@@ -5,10 +5,12 @@ require 'uri'
require 'fileutils'
require 'puppet/network/handler'
require 'puppet/util/diff'
+require 'puppet/util/checksums'
module Puppet
newtype(:file) do
include Puppet::Util::MethodHelper
+ include Puppet::Util::Checksums
@doc = "Manages local files, including setting ownership and
permissions, creation of both files and directories, and
retrieving entire files from remote servers. As Puppet matures, it
@@ -1031,54 +1033,38 @@ module Puppet
return [sourceobj, path.sub(/\/\//, '/')]
end
- # Write out the file. We open the file correctly, with all of the
- # uid and mode and such, and then yield the file handle for actual
- # writing.
- def write(property, usetmp = true)
- mode = self.should(:mode)
+ # 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?
+ # Use the appropriate checksum type -- md5, md5lite, etc.
+ sumtype = property(:checksum).checktype
+ checksum ||= "{#{sumtype}}" + property(:checksum).send(sumtype, content)
+ end
remove_existing(:file)
- # The temporary file
- path = nil
- if usetmp
- path = self[:path] + ".puppettmp"
- else
- path = self[:path]
- end
-
- # As the correct user and group
- write_if_writable(File.dirname(path)) do
- f = nil
- # Open our file with the correct modes
- if mode
- Puppet::Util.withumask(000) do
- f = File.open(path,
- File::CREAT|File::WRONLY|File::TRUNC, mode)
- end
- else
- f = File.open(path, File::CREAT|File::WRONLY|File::TRUNC)
- end
+ use_temporary_file = (content.length != 0)
+ path = self[:path]
+ path += ".puppettmp" if use_temporary_file
- # Yield it
- yield f
+ mode = self.should(:mode) # might be nil
+ umask = mode ? 000 : 022
- f.flush
- f.close
+ 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 usetmp
+ 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
- self.err "Could not rename tmp %s for replacing: %s" %
- [self[:path], detail]
+ self.err "Could not rename tmp %s for replacing: %s" % [self[:path], detail]
ensure
# Make sure the created file gets removed
- if FileTest.exists?(path)
- File.unlink(path)
- end
+ File.unlink(path) if FileTest.exists?(path)
end
end
@@ -1086,32 +1072,35 @@ module Puppet
property_fix
# And then update our checksum, so the next run doesn't find it.
- # FIXME This is extra work, because it's going to read the whole
- # file back in again.
- self.setchecksum
-
+ self.setchecksum(checksum)
end
-
- # Run the block as the specified user if the dir is writeable, else
- # run it as root (or the current user).
- def write_if_writable(dir)
- yield
- # We're getting different behaviors from different versions of ruby, so...
- # asroot = true
- # Puppet::Util::SUIDManager.asuser(asuser(), self.should(:group)) do
- # if FileTest.writable?(dir)
- # asroot = false
- # yield
- # end
- # end
- #
- # if asroot
- # yield
- # end
+
+ # Should we validate the checksum of the file we're writing?
+ def validate_checksum?
+ if sumparam = @parameters[:checksum]
+ return sumparam.checktype.to_s !~ /time/
+ else
+ return false
+ end
end
private
+ # Make sure the file we wrote out is what we think it is.
+ def fail_if_checksum_is_wrong(path, checksum)
+ if checksum =~ /^\{(\w+)\}.+/
+ sumtype = $1
+ else
+ # This shouldn't happen, but if it happens to, it's nicer
+ # to just use a default sumtype than fail.
+ sumtype = "md5"
+ end
+ newsum = property(:checksum).getsum(sumtype, path)
+ return if newsum == checksum
+
+ self.fail "File written to disk did not match checksum; discarding changes (%s vs %s)" % [checksum, newsum]
+ end
+
# Override the parent method, because we don't want to generate changes
# when the file is missing and there is no 'ensure' state.
def propertychanges(currentvalues)
diff --git a/lib/puppet/type/file/checksum.rb b/lib/puppet/type/file/checksum.rb
index 08f48ea21..debb5a7db 100755
--- a/lib/puppet/type/file/checksum.rb
+++ b/lib/puppet/type/file/checksum.rb
@@ -1,326 +1,271 @@
-# Keep a copy of the file checksums, and notify when they change.
+require 'puppet/util/checksums'
-# This state never actually modifies the system, it only notices when the system
+# Keep a copy of the file checksums, and notify when they change. This
+# property never actually modifies the system, it only notices when the system
# changes on its own.
-module Puppet
- Puppet.type(:file).newproperty(:checksum) do
- desc "How to check whether a file has changed. This state is used internally
- for file copying, but it can also be used to monitor files somewhat
- like Tripwire without managing the file contents in any way. You can
- specify that a file's checksum should be monitored and then subscribe to
- the file from another object and receive events to signify
- checksum changes, for instance."
+Puppet::Type.type(:file).newproperty(:checksum) do
+ include Puppet::Util::Checksums
- @event = :file_changed
+ desc "How to check whether a file has changed. This state is used internally
+ for file copying, but it can also be used to monitor files somewhat
+ like Tripwire without managing the file contents in any way. You can
+ specify that a file's checksum should be monitored and then subscribe to
+ the file from another object and receive events to signify
+ checksum changes, for instance."
- @unmanaged = true
+ @event = :file_changed
- @validtypes = %w{md5 md5lite timestamp mtime time}
+ @unmanaged = true
- def self.validtype?(type)
- @validtypes.include?(type)
- end
+ @validtypes = %w{md5 md5lite timestamp mtime time}
- @validtypes.each do |ctype|
- newvalue(ctype) do
- handlesum()
- end
- end
-
- str = @validtypes.join("|")
+ def self.validtype?(type)
+ @validtypes.include?(type)
+ end
- # This is here because Puppet sets this internally, using
- # {md5}......
- newvalue(/^\{#{str}\}/) do
+ @validtypes.each do |ctype|
+ newvalue(ctype) do
handlesum()
end
+ end
- newvalue(:nosum) do
- # nothing
- :nochange
- end
+ str = @validtypes.join("|")
- # If they pass us a sum type, behave normally, but if they pass
- # us a sum type + sum, stick the sum in the cache.
- munge do |value|
- if value =~ /^\{(\w+)\}(.+)$/
- type = symbolize($1)
- sum = $2
- cache(type, sum)
- return type
- else
- if FileTest.directory?(@resource[:path])
- return :time
- else
- return symbolize(value)
- end
- end
- end
+ # This is here because Puppet sets this internally, using
+ # {md5}......
+ newvalue(/^\{#{str}\}/) do
+ handlesum()
+ end
- # Store the checksum in the data cache, or retrieve it if only the
- # sum type is provided.
- def cache(type, sum = nil)
- unless type
- raise ArgumentError, "A type must be specified to cache a checksum"
- end
- type = symbolize(type)
- unless state = @resource.cached(:checksums)
- self.debug "Initializing checksum hash"
- state = {}
- @resource.cache(:checksums, state)
- end
+ newvalue(:nosum) do
+ # nothing
+ :nochange
+ end
- if sum
- unless sum =~ /\{\w+\}/
- sum = "{%s}%s" % [type, sum]
- end
- state[type] = sum
+ # If they pass us a sum type, behave normally, but if they pass
+ # us a sum type + sum, stick the sum in the cache.
+ munge do |value|
+ if value =~ /^\{(\w+)\}(.+)$/
+ type = symbolize($1)
+ sum = $2
+ cache(type, sum)
+ return type
+ else
+ if FileTest.directory?(@resource[:path])
+ return :time
else
- return state[type]
+ return symbolize(value)
end
end
+ end
- # Because source and content and whomever else need to set the checksum
- # and do the updating, we provide a simple mechanism for doing so.
- def checksum=(value)
- munge(@should)
- self.updatesum(value)
+ # Store the checksum in the data cache, or retrieve it if only the
+ # sum type is provided.
+ def cache(type, sum = nil)
+ unless type
+ raise ArgumentError, "A type must be specified to cache a checksum"
+ end
+ type = symbolize(type)
+ unless state = @resource.cached(:checksums)
+ self.debug "Initializing checksum hash"
+ state = {}
+ @resource.cache(:checksums, state)
end
- def checktype
- self.should || :md5
+ if sum
+ unless sum =~ /\{\w+\}/
+ sum = "{%s}%s" % [type, sum]
+ end
+ state[type] = sum
+ else
+ return state[type]
end
+ end
- # Checksums need to invert how changes are printed.
- def change_to_s(currentvalue, newvalue)
- begin
- if currentvalue == :absent
- return "defined '%s' as '%s'" %
- [self.name, self.currentsum]
- elsif newvalue == :absent
- return "undefined %s from '%s'" %
- [self.name, self.is_to_s(currentvalue)]
+ # Because source and content and whomever else need to set the checksum
+ # and do the updating, we provide a simple mechanism for doing so.
+ def checksum=(value)
+ munge(@should)
+ self.updatesum(value)
+ end
+
+ def checktype
+ self.should || :md5
+ end
+
+ # Checksums need to invert how changes are printed.
+ def change_to_s(currentvalue, newvalue)
+ begin
+ if currentvalue == :absent
+ return "defined '%s' as '%s'" %
+ [self.name, self.currentsum]
+ elsif newvalue == :absent
+ return "undefined %s from '%s'" %
+ [self.name, self.is_to_s(currentvalue)]
+ else
+ if defined? @cached and @cached
+ return "%s changed '%s' to '%s'" %
+ [self.name, @cached, self.is_to_s(currentvalue)]
else
- if defined? @cached and @cached
- return "%s changed '%s' to '%s'" %
- [self.name, @cached, self.is_to_s(currentvalue)]
- else
- return "%s changed '%s' to '%s'" %
- [self.name, self.currentsum, self.is_to_s(currentvalue)]
- end
+ return "%s changed '%s' to '%s'" %
+ [self.name, self.currentsum, self.is_to_s(currentvalue)]
end
- rescue Puppet::Error, Puppet::DevError
- raise
- rescue => detail
- raise Puppet::DevError, "Could not convert change %s to string: %s" %
- [self.name, detail]
end
+ rescue Puppet::Error, Puppet::DevError
+ raise
+ rescue => detail
+ raise Puppet::DevError, "Could not convert change %s to string: %s" %
+ [self.name, detail]
end
+ end
- def currentsum
- #"{%s}%s" % [self.should, cache(self.should)]
- cache(checktype())
- end
+ def currentsum
+ cache(checktype())
+ end
- # Retrieve the cached sum
- def getcachedsum
- hash = nil
- unless hash = @resource.cached(:checksums)
- hash = {}
- @resource.cache(:checksums, hash)
- end
+ # Retrieve the cached sum
+ def getcachedsum
+ hash = nil
+ unless hash = @resource.cached(:checksums)
+ hash = {}
+ @resource.cache(:checksums, hash)
+ end
- sumtype = self.should
+ sumtype = self.should
- if hash.include?(sumtype)
- #self.notice "Found checksum %s for %s" %
- # [hash[sumtype] ,@resource[:path]]
- sum = hash[sumtype]
+ if hash.include?(sumtype)
+ #self.notice "Found checksum %s for %s" %
+ # [hash[sumtype] ,@resource[:path]]
+ sum = hash[sumtype]
- unless sum =~ /^\{\w+\}/
- sum = "{%s}%s" % [sumtype, sum]
- end
- return sum
- elsif hash.empty?
- #self.notice "Could not find sum of type %s" % sumtype
- return :nosum
- else
- #self.notice "Found checksum for %s but not of type %s" %
- # [@resource[:path],sumtype]
- return :nosum
+ unless sum =~ /^\{\w+\}/
+ sum = "{%s}%s" % [sumtype, sum]
end
+ return sum
+ elsif hash.empty?
+ #self.notice "Could not find sum of type %s" % sumtype
+ return :nosum
+ else
+ #self.notice "Found checksum for %s but not of type %s" %
+ # [@resource[:path],sumtype]
+ return :nosum
end
+ end
- # Calculate the sum from disk.
- def getsum(checktype)
- sum = ""
-
- checktype = checktype.intern if checktype.is_a? String
- case checktype
- when :md5, :md5lite:
- if ! FileTest.file?(@resource[:path])
- @resource.debug "Cannot MD5 sum %s; using mtime" %
- [@resource.stat.ftype]
- sum = @resource.stat.mtime.to_s
- else
- begin
- File.open(@resource[:path]) { |file|
- hashfunc = Digest::MD5.new
- while (!file.eof)
- readBuf = file.read(512)
- hashfunc.update(readBuf)
- if checktype == :md5lite then
- break
- end
- end
- sum = hashfunc.hexdigest
- }
- rescue Errno::EACCES => detail
- self.notice "Cannot checksum %s: permission denied" %
- @resource[:path]
- @resource.delete(self.class.name)
- rescue => detail
- self.notice "Cannot checksum: %s" %
- detail
- @resource.delete(self.class.name)
- end
- end
- when :timestamp, :mtime:
- sum = @resource.stat.mtime.to_s
- #sum = File.stat(@resource[:path]).mtime.to_s
- when :time:
- sum = @resource.stat.ctime.to_s
- #sum = File.stat(@resource[:path]).ctime.to_s
- else
- raise Puppet::Error, "Invalid sum type %s" % checktype
- end
+ # Calculate the sum from disk.
+ def getsum(checktype, file = nil)
+ sum = ""
+
+ checktype = :mtime if checktype == :timestamp
+ checktype = :ctime if checktype == :time
+
+ file ||= @resource[:path]
+
+ return nil unless FileTest.exist?(file)
- return "{#{checktype}}" + sum.to_s
+ if ! FileTest.file?(file)
+ checktype = :mtime
end
+ method = checktype.to_s + "_file"
- # At this point, we don't actually modify the system, we modify
- # the stored state to reflect the current state, and then kick
- # off an event to mark any changes.
- def handlesum
- currentvalue = self.retrieve
- if currentvalue.nil?
- raise Puppet::Error, "Checksum state for %s is somehow nil" %
- @resource.title
- end
+ self.fail("Invalid checksum type %s" % checktype) unless respond_to?(method)
- if self.insync?(currentvalue)
- self.debug "Checksum is already in sync"
- return nil
- end
- # @resource.debug "%s(%s): after refresh, is '%s'" %
- # [self.class.name,@resource.name,@is]
+ return "{%s}%s" % [checktype, send(method, file)]
+ end
- # If we still can't retrieve a checksum, it means that
- # the file still doesn't exist
- if currentvalue == :absent
- # if they're copying, then we won't worry about the file
- # not existing yet
- unless @resource.property(:source)
- self.warning("File %s does not exist -- cannot checksum" %
- @resource[:path]
- )
- end
- return nil
- end
-
- # If the sums are different, then return an event.
- if self.updatesum(currentvalue)
- return :file_changed
- else
- return nil
- end
+ # At this point, we don't actually modify the system, we modify
+ # the stored state to reflect the current state, and then kick
+ # off an event to mark any changes.
+ def handlesum
+ currentvalue = self.retrieve
+ if currentvalue.nil?
+ raise Puppet::Error, "Checksum state for %s is somehow nil" %
+ @resource.title
end
- def insync?(currentvalue)
- @should = [checktype()]
- if cache(checktype())
- return currentvalue == currentsum()
- else
- # If there's no cached sum, then we don't want to generate
- # an event.
- return true
+ if self.insync?(currentvalue)
+ self.debug "Checksum is already in sync"
+ return nil
+ end
+ # If we still can't retrieve a checksum, it means that
+ # the file still doesn't exist
+ if currentvalue == :absent
+ # if they're copying, then we won't worry about the file
+ # not existing yet
+ unless @resource.property(:source)
+ self.warning("File %s does not exist -- cannot checksum" % @resource[:path])
end
+ return nil
end
- # Even though they can specify multiple checksums, the insync?
- # mechanism can really only test against one, so we'll just retrieve
- # the first specified sum type.
- def retrieve(usecache = false)
- # When the 'source' is retrieving, it passes "true" here so
- # that we aren't reading the file twice in quick succession, yo.
- currentvalue = currentsum()
- if usecache and currentvalue
- return currentvalue
- end
-
- stat = nil
- unless stat = @resource.stat
- return :absent
- end
-
- if stat.ftype == "link" and @resource[:links] != :follow
- self.debug "Not checksumming symlink"
- # @resource.delete(:checksum)
- return currentvalue
- end
-
- # Just use the first allowed check type
- currentvalue = getsum(checktype())
+ # If the sums are different, then return an event.
+ if self.updatesum(currentvalue)
+ return :file_changed
+ else
+ return nil
+ end
+ end
- # If there is no sum defined, then store the current value
- # into the cache, so that we're not marked as being
- # out of sync. We don't want to generate an event the first
- # time we get a sum.
- unless cache(checktype())
- # FIXME we should support an updatechecksums-like mechanism
- self.updatesum(currentvalue)
- end
-
- # @resource.debug "checksum state is %s" % self.is
+ def insync?(currentvalue)
+ @should = [checktype()]
+ if cache(checktype())
+ return currentvalue == currentsum()
+ else
+ # If there's no cached sum, then we don't want to generate
+ # an event.
+ return true
+ end
+ end
+
+ # Even though they can specify multiple checksums, the insync?
+ # mechanism can really only test against one, so we'll just retrieve
+ # the first specified sum type.
+ def retrieve(usecache = false)
+ # When the 'source' is retrieving, it passes "true" here so
+ # that we aren't reading the file twice in quick succession, yo.
+ currentvalue = currentsum()
+ return currentvalue if usecache and currentvalue
+
+ stat = nil
+ return :absent unless stat = @resource.stat
+
+ if stat.ftype == "link" and @resource[:links] != :follow
+ self.debug "Not checksumming symlink"
+ # @resource.delete(:checksum)
return currentvalue
end
- # Store the new sum to the state db.
- def updatesum(newvalue)
- result = false
+ # Just use the first allowed check type
+ currentvalue = getsum(checktype())
- if newvalue.is_a?(Symbol)
- raise Puppet::Error, "%s has invalid checksum" % @resource.title
- end
+ # If there is no sum defined, then store the current value
+ # into the cache, so that we're not marked as being
+ # out of sync. We don't want to generate an event the first
+ # time we get a sum.
+ self.updatesum(currentvalue) unless cache(checktype())
+
+ # @resource.debug "checksum state is %s" % self.is
+ return currentvalue
+ end
- # if we're replacing, vs. updating
- if sum = cache(checktype())
- # unless defined? @should
- # raise Puppet::Error.new(
- # ("@should is not initialized for %s, even though we " +
- # "found a checksum") % @resource[:path]
- # )
- # end
-
- if newvalue == sum
- return false
- end
+ # Store the new sum to the state db.
+ def updatesum(newvalue)
+ result = false
- self.debug "Replacing %s checksum %s with %s" %
- [@resource.title, sum, newvalue]
- # @resource.debug "currentvalue: %s; @should: %s" %
- # [newvalue,@should]
- result = true
- else
- @resource.debug "Creating checksum %s" % newvalue
- result = false
- end
+ # if we're replacing, vs. updating
+ if sum = cache(checktype())
+ return false if newvalue == sum
- # Cache the sum so the log message can be right if possible.
- @cached = sum
- cache(checktype(), newvalue)
- return result
+ self.debug "Replacing %s checksum %s with %s" % [@resource.title, sum, newvalue]
+ result = true
+ else
+ @resource.debug "Creating checksum %s" % newvalue
+ result = false
end
+
+ # Cache the sum so the log message can be right if possible.
+ @cached = sum
+ cache(checktype(), newvalue)
+ return result
end
end
-
diff --git a/lib/puppet/type/file/content.rb b/lib/puppet/type/file/content.rb
index 6dcda0aa6..687a83f14 100755
--- a/lib/puppet/type/file/content.rb
+++ b/lib/puppet/type/file/content.rb
@@ -74,12 +74,17 @@ module Puppet
end
end
+ # Make sure we're also managing the checksum property.
+ def should=(value)
+ super
+ @resource.newattr(:checksum) unless @resource.property(:checksum)
+ end
# Just write our content out to disk.
def sync
return_event = @resource.stat ? :file_changed : :file_created
- @resource.write(:content) { |f| f.print self.should }
+ @resource.write(self.should, :content)
return return_event
end
diff --git a/lib/puppet/type/file/ensure.rb b/lib/puppet/type/file/ensure.rb
index 3aa918f65..028a7083d 100755
--- a/lib/puppet/type/file/ensure.rb
+++ b/lib/puppet/type/file/ensure.rb
@@ -46,7 +46,7 @@ module Puppet
if property = (@resource.property(:content) || @resource.property(:source))
property.sync
else
- @resource.write(false) { |f| f.flush }
+ @resource.write("", :ensure)
mode = @resource.should(:mode)
end
return :file_created
@@ -67,14 +67,12 @@ module Puppet
"Cannot create %s; parent directory %s does not exist" %
[@resource[:path], parent]
end
- @resource.write_if_writable(parent) do
- if mode
- Puppet::Util.withumask(000) do
- Dir.mkdir(@resource[:path],mode)
- end
- else
- Dir.mkdir(@resource[:path])
+ if mode
+ Puppet::Util.withumask(000) do
+ Dir.mkdir(@resource[:path],mode)
end
+ else
+ Dir.mkdir(@resource[:path])
end
@resource.send(:property_fix)
@resource.setchecksum
diff --git a/lib/puppet/type/file/source.rb b/lib/puppet/type/file/source.rb
index a3e533c31..7fa5eb1a9 100755
--- a/lib/puppet/type/file/source.rb
+++ b/lib/puppet/type/file/source.rb
@@ -235,9 +235,7 @@ module Puppet
checks.delete(:checksum)
@resource[:check] = checks
- unless @resource.property(:checksum)
- @resource[:checksum] = :md5
- end
+ @resource[:checksum] = :md5 unless @resource.property(:checksum)
end
def sync
@@ -245,7 +243,7 @@ module Puppet
exists = File.exists?(@resource[:path])
- @resource.write(:source) { |f| f.print contents }
+ @resource.write(contents, :source, @stats[:checksum])
if exists
return :file_changed
diff --git a/lib/puppet/util/checksums.rb b/lib/puppet/util/checksums.rb
index 598b3adfa..15d2eadd1 100644
--- a/lib/puppet/util/checksums.rb
+++ b/lib/puppet/util/checksums.rb
@@ -55,7 +55,7 @@ module Puppet::Util::Checksums
end
# Return the :ctime of a file.
- def timestamp_file(filename)
+ def ctime_file(filename)
File.stat(filename).send(:ctime)
end