forked from puppetlabs/puppet
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
(PUP-3030) Flesh out implementation of P::F::Tempfile
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
Showing
1 changed file
with
171 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |