1#
2# tmpdir - retrieve temporary directory path
3#
4# $Id: tmpdir.rb 38348 2012-12-12 12:40:51Z nobu $
5#
6
7require 'fileutils'
8begin
9  require 'etc.so'
10rescue LoadError
11end
12
13class Dir
14
15  @@systmpdir ||= defined?(Etc.systmpdir) ? Etc.systmpdir : '/tmp'
16
17  ##
18  # Returns the operating system's temporary file path.
19
20  def Dir::tmpdir
21    if $SAFE > 0
22      tmp = @@systmpdir
23    else
24      tmp = nil
25      for dir in [ENV['TMPDIR'], ENV['TMP'], ENV['TEMP'], @@systmpdir, '/tmp', '.']
26        next if !dir
27        dir = File.expand_path(dir)
28        if stat = File.stat(dir) and stat.directory? and stat.writable? and
29            (!stat.world_writable? or stat.sticky?)
30          tmp = dir
31          break
32        end rescue nil
33      end
34      raise ArgumentError, "could not find a temporary directory" if !tmp
35      tmp
36    end
37  end
38
39  # Dir.mktmpdir creates a temporary directory.
40  #
41  # The directory is created with 0700 permission.
42  # Application should not change the permission to make the temporary directory accesible from other users.
43  #
44  # The prefix and suffix of the name of the directory is specified by
45  # the optional first argument, <i>prefix_suffix</i>.
46  # - If it is not specified or nil, "d" is used as the prefix and no suffix is used.
47  # - If it is a string, it is used as the prefix and no suffix is used.
48  # - If it is an array, first element is used as the prefix and second element is used as a suffix.
49  #
50  #  Dir.mktmpdir {|dir| dir is ".../d..." }
51  #  Dir.mktmpdir("foo") {|dir| dir is ".../foo..." }
52  #  Dir.mktmpdir(["foo", "bar"]) {|dir| dir is ".../foo...bar" }
53  #
54  # The directory is created under Dir.tmpdir or
55  # the optional second argument <i>tmpdir</i> if non-nil value is given.
56  #
57  #  Dir.mktmpdir {|dir| dir is "#{Dir.tmpdir}/d..." }
58  #  Dir.mktmpdir(nil, "/var/tmp") {|dir| dir is "/var/tmp/d..." }
59  #
60  # If a block is given,
61  # it is yielded with the path of the directory.
62  # The directory and its contents are removed
63  # using FileUtils.remove_entry before Dir.mktmpdir returns.
64  # The value of the block is returned.
65  #
66  #  Dir.mktmpdir {|dir|
67  #    # use the directory...
68  #    open("#{dir}/foo", "w") { ... }
69  #  }
70  #
71  # If a block is not given,
72  # The path of the directory is returned.
73  # In this case, Dir.mktmpdir doesn't remove the directory.
74  #
75  #  dir = Dir.mktmpdir
76  #  begin
77  #    # use the directory...
78  #    open("#{dir}/foo", "w") { ... }
79  #  ensure
80  #    # remove the directory.
81  #    FileUtils.remove_entry dir
82  #  end
83  #
84  def Dir.mktmpdir(prefix_suffix=nil, *rest)
85    path = Tmpname.create(prefix_suffix || "d", *rest) {|n| mkdir(n, 0700)}
86    if block_given?
87      begin
88        yield path
89      ensure
90        stat = File.stat(File.dirname(path))
91        if stat.world_writable? and !stat.sticky?
92          raise ArgumentError, "parent directory is world writable but not sticky"
93        end
94        FileUtils.remove_entry path
95      end
96    else
97      path
98    end
99  end
100
101  module Tmpname # :nodoc:
102    module_function
103
104    def tmpdir
105      Dir.tmpdir
106    end
107
108    def make_tmpname(prefix_suffix, n)
109      case prefix_suffix
110      when String
111        prefix = prefix_suffix
112        suffix = ""
113      when Array
114        prefix = prefix_suffix[0]
115        suffix = prefix_suffix[1]
116      else
117        raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}"
118      end
119      t = Time.now.strftime("%Y%m%d")
120      path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}"
121      path << "-#{n}" if n
122      path << suffix
123    end
124
125    def create(basename, *rest)
126      if opts = Hash.try_convert(rest[-1])
127        opts = opts.dup if rest.pop.equal?(opts)
128        max_try = opts.delete(:max_try)
129        opts = [opts]
130      else
131        opts = []
132      end
133      tmpdir, = *rest
134      if $SAFE > 0 and tmpdir.tainted?
135        tmpdir = '/tmp'
136      else
137        tmpdir ||= tmpdir()
138      end
139      n = nil
140      begin
141        path = File.join(tmpdir, make_tmpname(basename, n))
142        yield(path, n, *opts)
143      rescue Errno::EEXIST
144        n ||= 0
145        n += 1
146        retry if !max_try or n < max_try
147        raise "cannot generate temporary name using `#{basename}' under `#{tmpdir}'"
148      end
149      path
150    end
151  end
152end
153