diff options
author | Luke Kanies <luke@madstop.com> | 2008-02-21 23:18:40 -0500 |
---|---|---|
committer | Luke Kanies <luke@madstop.com> | 2008-02-21 23:18:40 -0500 |
commit | b06767ee2d7c22c27d746d3e8d1b6effa37deaa6 (patch) | |
tree | 1b31ca784215113e5962310299826616c8768cd7 /lib | |
parent | 5e18b8dc91b2313a96dd3a9ff9cb0a88bfe0d6a0 (diff) | |
download | puppet-b06767ee2d7c22c27d746d3e8d1b6effa37deaa6.tar.gz puppet-b06767ee2d7c22c27d746d3e8d1b6effa37deaa6.tar.xz puppet-b06767ee2d7c22c27d746d3e8d1b6effa37deaa6.zip |
Quashed commit of my fixes for #1010.
Diffstat (limited to 'lib')
-rw-r--r-- | lib/puppet/type/file.rb | 101 | ||||
-rwxr-xr-x | lib/puppet/type/file/checksum.rb | 487 | ||||
-rwxr-xr-x | lib/puppet/type/file/content.rb | 7 | ||||
-rwxr-xr-x | lib/puppet/type/file/ensure.rb | 14 | ||||
-rwxr-xr-x | lib/puppet/type/file/source.rb | 6 | ||||
-rw-r--r-- | lib/puppet/util/checksums.rb | 2 |
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 |