summaryrefslogtreecommitdiff
path: root/lib/tmpdir.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/tmpdir.rb')
-rw-r--r--lib/tmpdir.rb151
1 files changed, 94 insertions, 57 deletions
diff --git a/lib/tmpdir.rb b/lib/tmpdir.rb
index 18d4fb683d..f78fd721b7 100644
--- a/lib/tmpdir.rb
+++ b/lib/tmpdir.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
#
# tmpdir - retrieve temporary directory path
#
@@ -7,39 +8,51 @@
require 'fileutils'
begin
require 'etc.so'
-rescue LoadError
+rescue LoadError # rescue LoadError for miniruby
end
class Dir
- @@systmpdir ||= defined?(Etc.systmpdir) ? Etc.systmpdir : '/tmp'
+ # Class variables are inaccessible from non-main Ractor.
+ # And instance variables too, in Ruby 3.0.
##
# Returns the operating system's temporary file path.
+ #
+ # require 'tmpdir'
+ # Dir.tmpdir # => "/tmp"
- def Dir::tmpdir
- if $SAFE > 0
- tmp = @@systmpdir
- else
- tmp = nil
- for dir in [ENV['TMPDIR'], ENV['TMP'], ENV['TEMP'], @@systmpdir, '/tmp', '.']
- next if !dir
- dir = File.expand_path(dir)
- if stat = File.stat(dir) and stat.directory? and stat.writable? and
- (!stat.world_writable? or stat.sticky?)
- tmp = dir
- break
- end rescue nil
+ def self.tmpdir
+ Tmpname::TMPDIR_CANDIDATES.find do |name, dir|
+ unless dir
+ next if !(dir = ENV[name] rescue next) or dir.empty?
end
- raise ArgumentError, "could not find a temporary directory" if !tmp
- tmp
- end
+ dir = File.expand_path(dir)
+ stat = File.stat(dir) rescue next
+ case
+ when !stat.directory?
+ warn "#{name} is not a directory: #{dir}"
+ when !File.writable?(dir)
+ # We call File.writable?, not stat.writable?, because you can't tell if a dir is actually
+ # writable just from stat; OS mechanisms other than user/group/world bits can affect this.
+ warn "#{name} is not writable: #{dir}"
+ when stat.world_writable? && !stat.sticky?
+ warn "#{name} is world-writable: #{dir}"
+ else
+ break dir
+ end
+ end or raise ArgumentError, "could not find a temporary directory"
end
# Dir.mktmpdir creates a temporary directory.
#
+ # require 'tmpdir'
+ # Dir.mktmpdir {|dir|
+ # # use the directory
+ # }
+ #
# The directory is created with 0700 permission.
- # Application should not change the permission to make the temporary directory accesible from other users.
+ # Application should not change the permission to make the temporary directory accessible from other users.
#
# The prefix and suffix of the name of the directory is specified by
# the optional first argument, <i>prefix_suffix</i>.
@@ -65,7 +78,7 @@ class Dir
#
# Dir.mktmpdir {|dir|
# # use the directory...
- # open("#{dir}/foo", "w") { ... }
+ # open("#{dir}/foo", "w") { something using the file }
# }
#
# If a block is not given,
@@ -75,21 +88,28 @@ class Dir
# dir = Dir.mktmpdir
# begin
# # use the directory...
- # open("#{dir}/foo", "w") { ... }
+ # open("#{dir}/foo", "w") { something using the file }
# ensure
# # remove the directory.
# FileUtils.remove_entry dir
# end
#
- def Dir.mktmpdir(prefix_suffix=nil, *rest)
- path = Tmpname.create(prefix_suffix || "d", *rest) {|n| mkdir(n, 0700)}
- if block_given?
+ def self.mktmpdir(prefix_suffix=nil, *rest, **options, &block)
+ base = nil
+ path = Tmpname.create(prefix_suffix || "d", *rest, **options) {|path, _, _, d|
+ base = d
+ mkdir(path, 0700)
+ }
+ if block
begin
- yield path
+ yield path.dup
ensure
- stat = File.stat(File.dirname(path))
- if stat.world_writable? and !stat.sticky?
- raise ArgumentError, "parent directory is world writable but not sticky"
+ unless base
+ base = File.dirname(path)
+ stat = File.stat(base)
+ if stat.world_writable? and !stat.sticky?
+ raise ArgumentError, "parent directory is world writable but not sticky: #{base}"
+ end
end
FileUtils.remove_entry path
end
@@ -98,53 +118,70 @@ class Dir
end
end
+ # Temporary name generator
module Tmpname # :nodoc:
module_function
+ # System-wide temporary directory path
+ systmpdir = (defined?(Etc.systmpdir) ? Etc.systmpdir.freeze : '/tmp')
+
+ # Temporary directory candidates consisting of environment variable
+ # names or description and path pairs.
+ TMPDIR_CANDIDATES = [
+ 'TMPDIR', 'TMP', 'TEMP',
+ ['system temporary path', systmpdir],
+ %w[/tmp /tmp],
+ %w[. .],
+ ].each(&:freeze).freeze
+
def tmpdir
Dir.tmpdir
end
- 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}"
+ # Unusable characters as path name
+ UNUSABLE_CHARS = "^,-.0-9A-Z_a-z~"
+
+ # Dedicated random number generator
+ RANDOM = Object.new
+ class << RANDOM # :nodoc:
+ # Maximum random number
+ MAX = 36**6 # < 0x100000000
+
+ # Returns new random string upto 6 bytes
+ def next
+ (::Random.urandom(4).unpack1("L")%MAX).to_s(36)
end
- t = Time.now.strftime("%Y%m%d")
- path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}"
- path << "-#{n}" if n
- path << suffix
end
+ RANDOM.freeze
+ private_constant :RANDOM
- def create(basename, *rest)
- if opts = Hash.try_convert(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'
+ # Generates and yields random names to create a temporary name
+ def create(basename, tmpdir=nil, max_try: nil, **opts)
+ if tmpdir
+ origdir = tmpdir = File.path(tmpdir)
+ raise ArgumentError, "empty parent path" if tmpdir.empty?
else
- tmpdir ||= tmpdir()
+ tmpdir = tmpdir()
end
n = nil
+ prefix, suffix = basename
+ prefix = (String.try_convert(prefix) or
+ raise ArgumentError, "unexpected prefix: #{prefix.inspect}")
+ prefix = prefix.delete(UNUSABLE_CHARS)
+ suffix &&= (String.try_convert(suffix) or
+ raise ArgumentError, "unexpected suffix: #{suffix.inspect}")
+ suffix &&= suffix.delete(UNUSABLE_CHARS)
begin
- path = File.join(tmpdir, make_tmpname(basename, n))
- yield(path, n, *opts)
+ t = Time.now.strftime("%Y%m%d")
+ path = "#{prefix}#{t}-#{$$}-#{RANDOM.next}"\
+ "#{n ? %[-#{n}] : ''}#{suffix||''}"
+ path = File.join(tmpdir, path)
+ yield(path, n, opts, origdir)
rescue Errno::EEXIST
n ||= 0
n += 1
retry if !max_try or n < max_try
- raise "cannot generate temporary name using `#{basename}' under `#{tmpdir}'"
+ raise "cannot generate temporary name using '#{basename}' under '#{tmpdir}'"
end
path
end