Skip to content

Commit 319bef5

Browse files
committed
Update Uniquefile name generation
Commit 19cf4be added `Puppet::Util::Tempfile`, based on ruby 1.9.3p547. It was later renamed to `Uniquefile`. The code contained a bug whereby if any TEMP, TMP, etc environment variable was defined, but referred to a non-existent directory, then `Uniquefile.new` would raise and never fallback to other candidate temp directories. Ruby 2.0 added `Dir::Tmpname` as a way to generate a unique name. So just call that and delete the problematic `make_tmpname`, etc private methods. There have also been a number of fixes to Dir::Tmpname since 1.9.3: * Ignore empty TMP, etc env variables * Ignore TMP, etc env variables that refer to non-existent directories * Warn if TMP, etc exist, but are not directories, not writable, etc * Raising if we exhaust the list of candidate temp dirs * Restricting allowed characters to only /,-.0-9A-Z_a-z~/
1 parent d879bf8 commit 319bef5

File tree

3 files changed

+28
-85
lines changed

3 files changed

+28
-85
lines changed

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

+10-81
Original file line numberDiff line numberDiff line change
@@ -27,34 +27,26 @@ def self.open_tmp(identifier)
2727
end
2828
end
2929

30-
def initialize(basename, *rest)
31-
create_tmpname(basename, *rest) do |tmpname, _n, opts|
32-
mode = File::RDWR | File::CREAT | File::EXCL
33-
perm = 0o600
34-
if opts
35-
mode |= opts.delete(:mode) || 0
36-
opts[:perm] = perm
37-
perm = nil
38-
else
39-
opts = perm
40-
end
30+
def initialize(basename, tmpdir = nil, mode: 0)
31+
@mode = mode | File::RDWR | File::CREAT | File::EXCL
32+
33+
Dir::Tmpname.create(basename, tmpdir) do |tmpname, _n, opts|
34+
opts[:perm] = 0600
4135
self.class.locking(tmpname) do
42-
@tmpfile = File.open(tmpname, mode, opts)
36+
@tmpfile = File.open(tmpname, @mode, **opts)
4337
@tmpname = tmpname
4438
end
45-
@mode = mode & ~(File::CREAT | File::EXCL)
46-
perm or opts.freeze
47-
@opts = opts
39+
@opts = opts.freeze
4840
end
4941

5042
super(@tmpfile)
5143
end
5244

5345
# Opens or reopens the file with mode "r+".
5446
def open
55-
@tmpfile.close if @tmpfile
56-
@tmpfile = File.open(@tmpname, @mode, @opts)
57-
__setobj__(@tmpfile)
47+
_close
48+
mode = @mode & ~(File::CREAT|File::EXCL)
49+
@tmpfile = File.open(@tmpname, mode, **@opts)
5850
end
5951

6052
def _close
@@ -99,71 +91,8 @@ def path
9991

10092
private
10193

102-
def make_tmpname(prefix_suffix, n)
103-
case prefix_suffix
104-
when String
105-
prefix = prefix_suffix
106-
suffix = ""
107-
when Array
108-
prefix = prefix_suffix[0]
109-
suffix = prefix_suffix[1]
110-
else
111-
raise ArgumentError, _("unexpected prefix_suffix: %{value}") % { value: prefix_suffix.inspect }
112-
end
113-
t = Time.now.strftime("%Y%m%d")
114-
path = "#{prefix}#{t}-#{$PROCESS_ID}-#{rand(0x100000000).to_s(36)}"
115-
path << "-#{n}" if n
116-
path << suffix
117-
end
118-
119-
def create_tmpname(basename, *rest)
120-
opts = try_convert_to_hash(rest[-1])
121-
if opts
122-
opts = opts.dup if rest.pop.equal?(opts)
123-
max_try = opts.delete(:max_try)
124-
opts = [opts]
125-
else
126-
opts = []
127-
end
128-
tmpdir, = *rest
129-
tmpdir ||= tmpdir()
130-
n = nil
131-
begin
132-
path = File.join(tmpdir, make_tmpname(basename, n))
133-
yield(path, n, *opts)
134-
rescue Errno::EEXIST
135-
n ||= 0
136-
n += 1
137-
retry if !max_try or n < max_try
138-
raise _("cannot generate temporary name using `%{basename}' under `%{tmpdir}'") % { basename: basename, tmpdir: tmpdir }
139-
end
140-
path
141-
end
142-
143-
def try_convert_to_hash(h)
144-
h.to_hash
145-
rescue NoMethodError
146-
nil
147-
end
148-
14994
@@systmpdir ||= defined?(Etc.systmpdir) ? Etc.systmpdir : '/tmp'
15095

151-
def tmpdir
152-
tmp = '.'
153-
[ENV.fetch('TMPDIR', nil), ENV.fetch('TMP', nil), ENV.fetch('TEMP', nil), @@systmpdir, '/tmp'].each do |dir|
154-
stat = File.stat(dir) if dir
155-
begin
156-
if stat && stat.directory? && stat.writable?
157-
tmp = dir
158-
break
159-
end
160-
rescue
161-
nil
162-
end
163-
end
164-
File.expand_path(tmp)
165-
end
166-
16796
class << self
16897
# yields with locking for +tmpname+ and returns the result of the
16998
# block.

Diff for: spec/unit/file_system/uniquefile_spec.rb

+15
Original file line numberDiff line numberDiff line change
@@ -241,5 +241,20 @@ def tempfile(*args, &block)
241241
t.close
242242
expect(File.size(t.path)).to eq(5)
243243
end
244+
245+
it "ignores non-existent directories" do
246+
nonexistent = tmpdir('nonexistent')
247+
Dir.rmdir(nonexistent)
248+
valid = tmpdir('valid')
249+
# assuming directories are checked in this order: TMPDIR -> TMP -> TEMP
250+
Puppet::Util.withenv('TMPDIR' => nonexistent, 'TMP' => '', 'TEMP' => valid) do
251+
t = tempfile("foo")
252+
expect(t.path).to start_with(valid)
253+
end
254+
end
255+
256+
it "strips invalid characters" do
257+
expect(File.basename(tempfile("../../foo").path)).to start_with("....foo")
258+
end
244259
end
245260
end

Diff for: spec/unit/util/execution_spec.rb

+3-4
Original file line numberDiff line numberDiff line change
@@ -838,12 +838,11 @@ def expect_cwd_to_be(cwd)
838838
it "should raise if it fails to create a Uniquefile for stdout" do
839839
stdout = Puppet::FileSystem::Uniquefile.new('test')
840840
allow(Puppet::FileSystem::Uniquefile).to receive(:new)
841-
.and_raise(
842-
Errno::ENOENT,
843-
'No such file or directory @ rb_file_s_stat - C:\Users\ADMINI~1\AppData\Local\Temp\doesnotexist')
841+
.and_raise(ArgumentError, 'could not find a temporary directory')
842+
844843
expect {
845844
Puppet::Util::Execution.execute('test command')
846-
}.to raise_error(Errno::ENOENT, 'No such file or directory @ rb_file_s_stat - C:\Users\ADMINI~1\AppData\Local\Temp\doesnotexist')
845+
}.to raise_error(ArgumentError, 'could not find a temporary directory')
847846
end
848847
end
849848

0 commit comments

Comments
 (0)