# Basic classes for reading, writing, and emptying files. Not much # to see here. require 'puppet/util/selinux' require 'tempfile' require 'fileutils' class Puppet::Util::FileType attr_accessor :loaded, :path, :synced class FileReadError < Puppet::Error; end include Puppet::Util::SELinux class << self attr_accessor :name include Puppet::Util::ClassGen end # Create a new filetype. def self.newfiletype(name, &block) @filetypes ||= {} klass = genclass( name, :block => block, :prefix => "FileType", :hash => @filetypes ) # Rename the read and write methods, so that we're sure they # maintain the stats. klass.class_eval do # Rename the read method define_method(:real_read, instance_method(:read)) define_method(:read) do begin val = real_read @loaded = Time.now if val return val.gsub(/# HEADER.*\n/,'') else return "" end rescue Puppet::Error => detail raise rescue => detail message = _("%{klass} could not read %{path}: %{detail}") % { klass: self.class, path: @path, detail: detail } Puppet.log_exception(detail, message) raise Puppet::Error, message, detail.backtrace end end # And then the write method define_method(:real_write, instance_method(:write)) define_method(:write) do |text| begin val = real_write(text) @synced = Time.now return val rescue Puppet::Error => detail raise rescue => detail message = _("%{klass} could not write %{path}: %{detail}") % { klass: self.class, path: @path, detail: detail } Puppet.log_exception(detail, message) raise Puppet::Error, message, detail.backtrace end end end end def self.filetype(type) @filetypes[type] end # Pick or create a filebucket to use. def bucket @bucket ||= Puppet::Type.type(:filebucket).mkdefaultbucket.bucket end def initialize(path, default_mode = nil) raise ArgumentError.new(_("Path is nil")) if path.nil? @path = path @default_mode = default_mode end # Arguments that will be passed to the execute method. Will set the uid # to the target user if the target user and the current user are not # the same def cronargs if uid = Puppet::Util.uid(@path) and uid == Puppet::Util::SUIDManager.uid {:failonfail => true, :combine => true} else {:failonfail => true, :combine => true, :uid => @path} end end # Operate on plain files. newfiletype(:flat) do # Back the file up before replacing it. def backup bucket.backup(@path) if Puppet::FileSystem.exist?(@path) end # Read the file. def read if Puppet::FileSystem.exist?(@path) # this code path is used by many callers so the original default is # being explicitly preserved Puppet::FileSystem.read(@path, :encoding => Encoding.default_external) else return nil end end # Remove the file. def remove Puppet::FileSystem.unlink(@path) if Puppet::FileSystem.exist?(@path) end # Overwrite the file. def write(text) # this file is managed by the OS and should be using system encoding tf = Tempfile.new("puppet", :encoding => Encoding.default_external) tf.print text; tf.flush File.chmod(@default_mode, tf.path) if @default_mode FileUtils.cp(tf.path, @path) tf.close # If SELinux is present, we need to ensure the file has its expected context set_selinux_default_context(@path) end end # Operate on plain files. newfiletype(:ram) do @@tabs = {} def self.clear @@tabs.clear end def initialize(path, default_mode = nil) # default_mode is meaningless for this filetype, # supported only for compatibility with :flat super @@tabs[@path] ||= "" end # Read the file. def read Puppet.info _("Reading %{path} from RAM") % { path: @path } @@tabs[@path] end # Remove the file. def remove Puppet.info _("Removing %{path} from RAM") % { path: @path } @@tabs[@path] = "" end # Overwrite the file. def write(text) Puppet.info _("Writing %{path} to RAM") % { path: @path } @@tabs[@path] = text end end # Handle Linux-style cron tabs. newfiletype(:crontab) do def initialize(user) self.path = user end def path=(user) begin @uid = Puppet::Util.uid(user) rescue Puppet::Error => detail raise FileReadError, _("Could not retrieve user %{user}: %{detail}") % { user: user, detail: detail }, detail.backtrace end # XXX We have to have the user name, not the uid, because some # systems *cough*linux*cough* require it that way @path = user end # Read a specific @path's cron tab. def read %x{#{cmdbase} -l 2>/dev/null} end # Remove a specific @path's cron tab. def remove if %w{Darwin FreeBSD DragonFly}.include?(Facter.value("operatingsystem")) %x{/bin/echo yes | #{cmdbase} -r 2>/dev/null} else %x{#{cmdbase} -r 2>/dev/null} end end # Overwrite a specific @path's cron tab; must be passed the @path name # and the text with which to create the cron tab. def write(text) # this file is managed by the OS and should be using system encoding IO.popen("#{cmdbase()} -", "w", :encoding => Encoding.default_external) { |p| p.print text } end private # Only add the -u flag when the @path is different. Fedora apparently # does not think I should be allowed to set the @path to my own user name def cmdbase if @uid == Puppet::Util::SUIDManager.uid || Facter.value(:operatingsystem) == "HP-UX" return "crontab" else return "crontab -u #{@path}" end end end # SunOS has completely different cron commands; this class implements # its versions. newfiletype(:suntab) do # Read a specific @path's cron tab. def read Puppet::Util::Execution.execute(%w{crontab -l}, cronargs) rescue => detail case detail.to_s when /can't open your crontab/ return "" when /you are not authorized to use cron/ raise FileReadError, _("User %{path} not authorized to use cron") % { path: @path }, detail.backtrace else raise FileReadError, _("Could not read crontab for %{path}: %{detail}") % { path: @path, detail: detail }, detail.backtrace end end # Remove a specific @path's cron tab. def remove Puppet::Util::Execution.execute(%w{crontab -r}, cronargs) rescue => detail raise FileReadError, _("Could not remove crontab for %{path}: %{detail}") % { path: @path, detail: detail }, detail.backtrace end # Overwrite a specific @path's cron tab; must be passed the @path name # and the text with which to create the cron tab. def write(text) # this file is managed by the OS and should be using system encoding output_file = Tempfile.new("puppet_suntab", :encoding => Encoding.default_external) begin output_file.print text output_file.close # We have to chown the stupid file to the user. File.chown(Puppet::Util.uid(@path), nil, output_file.path) Puppet::Util::Execution.execute(["crontab", output_file.path], cronargs) rescue => detail raise FileReadError, _("Could not write crontab for %{path}: %{detail}") % { path: @path, detail: detail }, detail.backtrace ensure output_file.close output_file.unlink end end end # Support for AIX crontab with output different than suntab's crontab command. newfiletype(:aixtab) do # Read a specific @path's cron tab. def read Puppet::Util::Execution.execute(%w{crontab -l}, cronargs) rescue => detail case detail.to_s when /Cannot open a file in the .* directory/ return "" when /You are not authorized to use the cron command/ raise FileReadError, _("User %{path} not authorized to use cron") % { path: @path }, detail.backtrace else raise FileReadError, _("Could not read crontab for %{path}: %{detail}") % { path: @path, detail: detail }, detail.backtrace end end # Remove a specific @path's cron tab. def remove Puppet::Util::Execution.execute(%w{crontab -r}, cronargs) rescue => detail raise FileReadError, _("Could not remove crontab for %{path}: %{detail}") % { path: @path, detail: detail }, detail.backtrace end # Overwrite a specific @path's cron tab; must be passed the @path name # and the text with which to create the cron tab. def write(text) # this file is managed by the OS and should be using system encoding output_file = Tempfile.new("puppet_aixtab", :encoding => Encoding.default_external) begin output_file.print text output_file.close # We have to chown the stupid file to the user. File.chown(Puppet::Util.uid(@path), nil, output_file.path) Puppet::Util::Execution.execute(["crontab", output_file.path], cronargs) rescue => detail raise FileReadError, _("Could not write crontab for %{path}: %{detail}") % { path: @path, detail: detail }, detail.backtrace ensure output_file.close output_file.unlink end end end end