1 # frozen_string_literal: true
3 # tmpdir - retrieve temporary directory path
11 rescue LoadError # rescue LoadError for miniruby
16 # Class variables are inaccessible from non-main Ractor.
17 # And instance variables too, in Ruby 3.0.
19 # System-wide temporary directory path
20 SYSTMPDIR = (defined?(Etc.systmpdir) ? Etc.systmpdir.freeze : '/tmp')
21 private_constant :SYSTMPDIR
24 # Returns the operating system's temporary file path.
27 # Dir.tmpdir # => "/tmp"
30 ['TMPDIR', 'TMP', 'TEMP', ['system temporary path', SYSTMPDIR], ['/tmp']*2, ['.']*2].find do |name, dir|
32 next if !(dir = ENV[name] rescue next) or dir.empty?
34 dir = File.expand_path(dir)
35 stat = File.stat(dir) rescue next
38 warn "#{name} is not a directory: #{dir}"
40 warn "#{name} is not writable: #{dir}"
41 when stat.world_writable? && !stat.sticky?
42 warn "#{name} is world-writable: #{dir}"
46 end or raise ArgumentError, "could not find a temporary directory"
49 # Dir.mktmpdir creates a temporary directory.
56 # The directory is created with 0700 permission.
57 # Application should not change the permission to make the temporary directory accessible from other users.
59 # The prefix and suffix of the name of the directory is specified by
60 # the optional first argument, <i>prefix_suffix</i>.
61 # - If it is not specified or nil, "d" is used as the prefix and no suffix is used.
62 # - If it is a string, it is used as the prefix and no suffix is used.
63 # - If it is an array, first element is used as the prefix and second element is used as a suffix.
65 # Dir.mktmpdir {|dir| dir is ".../d..." }
66 # Dir.mktmpdir("foo") {|dir| dir is ".../foo..." }
67 # Dir.mktmpdir(["foo", "bar"]) {|dir| dir is ".../foo...bar" }
69 # The directory is created under Dir.tmpdir or
70 # the optional second argument <i>tmpdir</i> if non-nil value is given.
72 # Dir.mktmpdir {|dir| dir is "#{Dir.tmpdir}/d..." }
73 # Dir.mktmpdir(nil, "/var/tmp") {|dir| dir is "/var/tmp/d..." }
75 # If a block is given,
76 # it is yielded with the path of the directory.
77 # The directory and its contents are removed
78 # using FileUtils.remove_entry before Dir.mktmpdir returns.
79 # The value of the block is returned.
82 # # use the directory...
83 # open("#{dir}/foo", "w") { something using the file }
86 # If a block is not given,
87 # The path of the directory is returned.
88 # In this case, Dir.mktmpdir doesn't remove the directory.
92 # # use the directory...
93 # open("#{dir}/foo", "w") { something using the file }
95 # # remove the directory.
96 # FileUtils.remove_entry dir
99 def self.mktmpdir(prefix_suffix=nil, *rest, **options)
101 path = Tmpname.create(prefix_suffix || "d", *rest, **options) {|path, _, _, d|
110 base = File.dirname(path)
111 stat = File.stat(base)
112 if stat.world_writable? and !stat.sticky?
113 raise ArgumentError, "parent directory is world writable but not sticky: #{base}"
116 FileUtils.remove_entry path
123 # Temporary name generator
124 module Tmpname # :nodoc:
131 # Unusable characters as path name
132 UNUSABLE_CHARS = "^,-.0-9A-Z_a-z~"
134 # Dedicated random number generator
136 class << RANDOM # :nodoc:
137 # Maximum random number
138 MAX = 36**6 # < 0x100000000
140 # Returns new random string upto 6 bytes
142 (::Random.urandom(4).unpack1("L")%MAX).to_s(36)
146 private_constant :RANDOM
148 # Generates and yields random names to create a temporary name
149 def create(basename, tmpdir=nil, max_try: nil, **opts)
153 prefix, suffix = basename
154 prefix = (String.try_convert(prefix) or
155 raise ArgumentError, "unexpected prefix: #{prefix.inspect}")
156 prefix = prefix.delete(UNUSABLE_CHARS)
157 suffix &&= (String.try_convert(suffix) or
158 raise ArgumentError, "unexpected suffix: #{suffix.inspect}")
159 suffix &&= suffix.delete(UNUSABLE_CHARS)
161 t = Time.now.strftime("%Y%m%d")
162 path = "#{prefix}#{t}-#{$$}-#{RANDOM.next}"\
163 "#{n ? %[-#{n}] : ''}#{suffix||''}"
164 path = File.join(tmpdir, path)
165 yield(path, n, opts, origdir)
169 retry if !max_try or n < max_try
170 raise "cannot generate temporary name using '#{basename}' under '#{tmpdir}'"