Skip to content

Commit 5fda962

Browse files
hsbtclaude
andcommitted
Implement Pathname.mktmpdir in builtin Pathname
Copy the tmpdir lookup logic from Dir.tmpdir (lib/tmpdir.rb) and the name generation logic from Dir::Tmpname.create into pathname_builtin.rb to remove the runtime dependency on the tmpdir and fileutils libraries. The implementation preserves the same candidate order, warning messages, world-writable parent directory checks, and cleanup behavior. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 5d4fff6 commit 5fda962

2 files changed

Lines changed: 76 additions & 42 deletions

File tree

lib/pathname.rb

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -9,45 +9,3 @@
99
#
1010
# For documentation, see class Pathname.
1111
#
12-
class Pathname # * tmpdir *
13-
# call-seq:
14-
# Pathname.mktmpdir -> new_pathname
15-
# Pathname.mktmpdir {|pathname| ... } -> object
16-
#
17-
# Creates:
18-
#
19-
# - A temporary directory via Dir.mktmpdir.
20-
# - A \Pathname object that contains the path to that directory.
21-
#
22-
# With no block given, returns the created pathname;
23-
# the caller should delete the created directory when it is no longer needed
24-
# (FileUtils.rm_r is a convenient method for the deletion):
25-
#
26-
# pathname = Pathname.mktmpdir
27-
# dirpath = pathname.to_s
28-
# Dir.exist?(dirpath) # => true
29-
# # Do something with the directory.
30-
# require 'fileutils'
31-
# FileUtils.rm_r(dirpath)
32-
#
33-
# With a block given, calls the block with the created pathname;
34-
# on block exit, automatically deletes the created directory and all its contents;
35-
# returns the block's exit value:
36-
#
37-
# pathname = Pathname.mktmpdir do |p|
38-
# # Do something with the directory.
39-
# p
40-
# end
41-
# Dir.exist?(pathname.to_s) # => false
42-
def self.mktmpdir
43-
require 'tmpdir' unless defined?(Dir.mktmpdir)
44-
if block_given?
45-
Dir.mktmpdir do |dir|
46-
dir = self.new(dir)
47-
yield dir
48-
end
49-
else
50-
self.new(Dir.mktmpdir)
51-
end
52-
end
53-
end

pathname_builtin.rb

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1400,6 +1400,82 @@ def rmtree(noop: nil, verbose: nil, secure: nil)
14001400
self
14011401
end
14021402

1403+
# call-seq:
1404+
# Pathname.mktmpdir -> new_pathname
1405+
# Pathname.mktmpdir {|pathname| ... } -> object
1406+
#
1407+
# Creates a temporary directory and returns a Pathname for it.
1408+
#
1409+
# The previous implementation delegated to Dir.mktmpdir and required
1410+
# the tmpdir library. This builtin implementation removes that dependency.
1411+
#
1412+
# With no block given, returns the created pathname;
1413+
# the caller should delete the created directory when it is no longer needed:
1414+
#
1415+
# pathname = Pathname.mktmpdir
1416+
# Dir.exist?(pathname.to_s) # => true
1417+
#
1418+
# With a block given, calls the block with the created pathname;
1419+
# on block exit, automatically deletes the created directory and all its contents;
1420+
# returns the block's exit value:
1421+
#
1422+
# Pathname.mktmpdir do |dir|
1423+
# # Do something with dir.
1424+
# end
1425+
#
1426+
def self.mktmpdir
1427+
systmpdir = (defined?(Etc.systmpdir) ? Etc.systmpdir.freeze : '/tmp')
1428+
candidates = [
1429+
'TMPDIR', 'TMP', 'TEMP',
1430+
['system temporary path', systmpdir],
1431+
%w[/tmp /tmp],
1432+
%w[. .],
1433+
]
1434+
tmpdir = candidates.find do |name, dir|
1435+
unless dir
1436+
next if !(dir = ENV[name] rescue next) or dir.empty?
1437+
end
1438+
dir = File.expand_path(dir)
1439+
stat = File.stat(dir) rescue next
1440+
case
1441+
when !stat.directory?
1442+
warn "#{name} is not a directory: #{dir}"
1443+
when !File.writable?(dir)
1444+
warn "#{name} is not writable: #{dir}"
1445+
when stat.world_writable? && !stat.sticky?
1446+
warn "#{name} is world-writable: #{dir}"
1447+
else
1448+
break dir
1449+
end
1450+
end or raise ArgumentError, "could not find a temporary directory"
1451+
1452+
t = Time.now.strftime("%Y%m%d")
1453+
n = nil
1454+
begin
1455+
name = "d#{t}-#{$$}-#{Random.urandom(4).unpack1("L").%(36**6).to_s(36)}#{n ? "-#{n}" : ''}"
1456+
dir = File.join(tmpdir, name)
1457+
Dir.mkdir(dir, 0700)
1458+
rescue Errno::EEXIST
1459+
n = (n || 0) + 1
1460+
retry
1461+
end
1462+
1463+
path = new(dir)
1464+
if block_given?
1465+
begin
1466+
yield path
1467+
ensure
1468+
stat = File.stat(tmpdir)
1469+
if stat.world_writable? && !stat.sticky?
1470+
raise ArgumentError, "parent directory is world writable but not sticky: #{tmpdir}"
1471+
end
1472+
path.send(:remove_entry, dir, false)
1473+
end
1474+
else
1475+
path
1476+
end
1477+
end
1478+
14031479
private
14041480

14051481
def remove_entry(path, force = true) # :nodoc:

0 commit comments

Comments
 (0)