Skip to content

Commit

Permalink
(PUP-3030) Flesh out implementation of P::F::Tempfile
Browse files Browse the repository at this point in the history
This commit changes `Puppet::FileSystem::Tempfile` so that
it no longer subclasses the built-in `Tempfile`.  The goal
here is to give us more control over the life cycle of the
files when used on the server.

The implementation is based on the Ruby 1.9.3 implementation of
`Tempfile`, but strips out some things like the finalizer
and methods that we don't need / use.

Note that we had to also port over a few methods from `Dir` in
order to support ruby 1.8.7.  Relevant source code is here:

https://github.com/ruby/ruby/blob/v1_9_3_547/lib/tempfile.rb
https://github.com/ruby/ruby/blob/v1_9_3_547/lib/tmpdir.rb

In its current incarnation the class is probably poorly
named because it no longer makes any attempt to automagically
unlink the file at some point in the future.  I intend
to submit a follow-up commit that renames the class to make
this more obvious.
  • Loading branch information
cprice404 committed Aug 18, 2014
1 parent 3255aa6 commit 19cf4be
Showing 1 changed file with 171 additions and 10 deletions.
181 changes: 171 additions & 10 deletions lib/puppet/file_system/tempfile.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,180 @@
require 'tempfile'
require 'puppet/file_system'
require 'delegate'
require 'tmpdir'

class Puppet::FileSystem::Tempfile < ::Tempfile

# Variation of Tempfile.open which ensures that the tempfile is closed and
# unlinked before returning
#
class Puppet::FileSystem::Tempfile < DelegateClass(File)
# @param identifier [String] additional part of generated pathname
# @yieldparam file [File] the temporary file object
# @return result of the passed block
# @api private
def self.open(identifier)
file = Puppet::FileSystem::Tempfile.new(identifier)
yield file
f = new(identifier)
yield f
ensure
file.close!
f.close!
end

def initialize(basename, *rest)
create_tmpname(basename, *rest) do |tmpname, n, opts|
mode = File::RDWR|File::CREAT|File::EXCL
perm = 0600
if opts
mode |= opts.delete(:mode) || 0
opts[:perm] = perm
perm = nil
else
opts = perm
end
self.class.locking(tmpname) do
@tmpfile = File.open(tmpname, mode, opts)
@tmpname = tmpname
end
@mode = mode & ~(File::CREAT|File::EXCL)
perm or opts.freeze
@opts = opts
end

super(@tmpfile)
end

# Opens or reopens the file with mode "r+".
def open
@tmpfile.close if @tmpfile
@tmpfile = File.open(@tmpname, @mode, @opts)
__setobj__(@tmpfile)
end

def _close
begin
@tmpfile.close if @tmpfile
ensure
@tmpfile = nil
end
end
protected :_close

def close(unlink_now=false)
if unlink_now
close!
else
_close
end
end

def close!
_close
unlink
end

def unlink
return unless @tmpname
begin
File.unlink(@tmpname)
rescue Errno::ENOENT
rescue Errno::EACCES
# may not be able to unlink on Windows; just ignore
return
end
@tmpname = nil
end
alias delete unlink

# Returns the full path name of the temporary file.
# This will be nil if #unlink has been called.
def path
@tmpname
end
end

private

def make_tmpname(prefix_suffix, n)
case prefix_suffix
when String
prefix = prefix_suffix
suffix = ""
when Array
prefix = prefix_suffix[0]
suffix = prefix_suffix[1]
else
raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}"
end
t = Time.now.strftime("%Y%m%d")
path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}"
path << "-#{n}" if n
path << suffix
end

def create_tmpname(basename, *rest)
if opts = try_convert_to_hash(rest[-1])
opts = opts.dup if rest.pop.equal?(opts)
max_try = opts.delete(:max_try)
opts = [opts]
else
opts = []
end
tmpdir, = *rest
if $SAFE > 0 and tmpdir.tainted?
tmpdir = '/tmp'
else
tmpdir ||= tmpdir()
end
n = nil
begin
path = File.expand_path(make_tmpname(basename, n), tmpdir)
yield(path, n, *opts)
rescue Errno::EEXIST
n ||= 0
n += 1
retry if !max_try or n < max_try
raise "cannot generate temporary name using `#{basename}' under `#{tmpdir}'"
end
path
end

def try_convert_to_hash(h)
begin
h.to_hash
rescue NoMethodError => e
nil
end
end

@@systmpdir ||= defined?(Etc.systmpdir) ? Etc.systmpdir : '/tmp'

def tmpdir
tmp = '.'
if $SAFE > 0
tmp = @@systmpdir
else
for dir in [ENV['TMPDIR'], ENV['TMP'], ENV['TEMP'], @@systmpdir, '/tmp']
if dir and stat = File.stat(dir) and stat.directory? and stat.writable?
tmp = dir
break
end rescue nil
end
File.expand_path(tmp)
end
end


class << self
# yields with locking for +tmpname+ and returns the result of the
# block.
def locking(tmpname)
lock = tmpname + '.lock'
mkdir(lock)
yield
ensure
rmdir(lock) if lock
end

def mkdir(*args)
Dir.mkdir(*args)
end

def rmdir(*args)
Dir.rmdir(*args)
end
end

end

0 comments on commit 19cf4be

Please sign in to comment.