Skip to content

Commit 19cf4be

Browse files
committed
(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.
1 parent 3255aa6 commit 19cf4be

File tree

1 file changed

+171
-10
lines changed

1 file changed

+171
-10
lines changed

Diff for: lib/puppet/file_system/tempfile.rb

+171-10
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,180 @@
1-
require 'tempfile'
21
require 'puppet/file_system'
2+
require 'delegate'
3+
require 'tmpdir'
34

4-
class Puppet::FileSystem::Tempfile < ::Tempfile
5-
6-
# Variation of Tempfile.open which ensures that the tempfile is closed and
7-
# unlinked before returning
8-
#
5+
class Puppet::FileSystem::Tempfile < DelegateClass(File)
96
# @param identifier [String] additional part of generated pathname
107
# @yieldparam file [File] the temporary file object
118
# @return result of the passed block
129
# @api private
1310
def self.open(identifier)
14-
file = Puppet::FileSystem::Tempfile.new(identifier)
15-
yield file
11+
f = new(identifier)
12+
yield f
1613
ensure
17-
file.close!
14+
f.close!
15+
end
16+
17+
def initialize(basename, *rest)
18+
create_tmpname(basename, *rest) do |tmpname, n, opts|
19+
mode = File::RDWR|File::CREAT|File::EXCL
20+
perm = 0600
21+
if opts
22+
mode |= opts.delete(:mode) || 0
23+
opts[:perm] = perm
24+
perm = nil
25+
else
26+
opts = perm
27+
end
28+
self.class.locking(tmpname) do
29+
@tmpfile = File.open(tmpname, mode, opts)
30+
@tmpname = tmpname
31+
end
32+
@mode = mode & ~(File::CREAT|File::EXCL)
33+
perm or opts.freeze
34+
@opts = opts
35+
end
36+
37+
super(@tmpfile)
38+
end
39+
40+
# Opens or reopens the file with mode "r+".
41+
def open
42+
@tmpfile.close if @tmpfile
43+
@tmpfile = File.open(@tmpname, @mode, @opts)
44+
__setobj__(@tmpfile)
45+
end
46+
47+
def _close
48+
begin
49+
@tmpfile.close if @tmpfile
50+
ensure
51+
@tmpfile = nil
52+
end
53+
end
54+
protected :_close
55+
56+
def close(unlink_now=false)
57+
if unlink_now
58+
close!
59+
else
60+
_close
61+
end
62+
end
63+
64+
def close!
65+
_close
66+
unlink
67+
end
68+
69+
def unlink
70+
return unless @tmpname
71+
begin
72+
File.unlink(@tmpname)
73+
rescue Errno::ENOENT
74+
rescue Errno::EACCES
75+
# may not be able to unlink on Windows; just ignore
76+
return
77+
end
78+
@tmpname = nil
79+
end
80+
alias delete unlink
81+
82+
# Returns the full path name of the temporary file.
83+
# This will be nil if #unlink has been called.
84+
def path
85+
@tmpname
1886
end
19-
end
87+
88+
private
89+
90+
def make_tmpname(prefix_suffix, n)
91+
case prefix_suffix
92+
when String
93+
prefix = prefix_suffix
94+
suffix = ""
95+
when Array
96+
prefix = prefix_suffix[0]
97+
suffix = prefix_suffix[1]
98+
else
99+
raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}"
100+
end
101+
t = Time.now.strftime("%Y%m%d")
102+
path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}"
103+
path << "-#{n}" if n
104+
path << suffix
105+
end
106+
107+
def create_tmpname(basename, *rest)
108+
if opts = try_convert_to_hash(rest[-1])
109+
opts = opts.dup if rest.pop.equal?(opts)
110+
max_try = opts.delete(:max_try)
111+
opts = [opts]
112+
else
113+
opts = []
114+
end
115+
tmpdir, = *rest
116+
if $SAFE > 0 and tmpdir.tainted?
117+
tmpdir = '/tmp'
118+
else
119+
tmpdir ||= tmpdir()
120+
end
121+
n = nil
122+
begin
123+
path = File.expand_path(make_tmpname(basename, n), tmpdir)
124+
yield(path, n, *opts)
125+
rescue Errno::EEXIST
126+
n ||= 0
127+
n += 1
128+
retry if !max_try or n < max_try
129+
raise "cannot generate temporary name using `#{basename}' under `#{tmpdir}'"
130+
end
131+
path
132+
end
133+
134+
def try_convert_to_hash(h)
135+
begin
136+
h.to_hash
137+
rescue NoMethodError => e
138+
nil
139+
end
140+
end
141+
142+
@@systmpdir ||= defined?(Etc.systmpdir) ? Etc.systmpdir : '/tmp'
143+
144+
def tmpdir
145+
tmp = '.'
146+
if $SAFE > 0
147+
tmp = @@systmpdir
148+
else
149+
for dir in [ENV['TMPDIR'], ENV['TMP'], ENV['TEMP'], @@systmpdir, '/tmp']
150+
if dir and stat = File.stat(dir) and stat.directory? and stat.writable?
151+
tmp = dir
152+
break
153+
end rescue nil
154+
end
155+
File.expand_path(tmp)
156+
end
157+
end
158+
159+
160+
class << self
161+
# yields with locking for +tmpname+ and returns the result of the
162+
# block.
163+
def locking(tmpname)
164+
lock = tmpname + '.lock'
165+
mkdir(lock)
166+
yield
167+
ensure
168+
rmdir(lock) if lock
169+
end
170+
171+
def mkdir(*args)
172+
Dir.mkdir(*args)
173+
end
174+
175+
def rmdir(*args)
176+
Dir.rmdir(*args)
177+
end
178+
end
179+
180+
end

0 commit comments

Comments
 (0)