1#
2# tempfile - manipulates temporary files
3#
4# $Id: tempfile.rb 43229 2013-10-09 16:11:16Z nagachika $
5#
6
7require 'delegate'
8require 'tmpdir'
9require 'thread'
10
11# A utility class for managing temporary files. When you create a Tempfile
12# object, it will create a temporary file with a unique filename. A Tempfile
13# objects behaves just like a File object, and you can perform all the usual
14# file operations on it: reading data, writing data, changing its permissions,
15# etc. So although this class does not explicitly document all instance methods
16# supported by File, you can in fact call any File instance method on a
17# Tempfile object.
18#
19# == Synopsis
20#
21#   require 'tempfile'
22#
23#   file = Tempfile.new('foo')
24#   file.path      # => A unique filename in the OS's temp directory,
25#                  #    e.g.: "/tmp/foo.24722.0"
26#                  #    This filename contains 'foo' in its basename.
27#   file.write("hello world")
28#   file.rewind
29#   file.read      # => "hello world"
30#   file.close
31#   file.unlink    # deletes the temp file
32#
33# == Good practices
34#
35# === Explicit close
36#
37# When a Tempfile object is garbage collected, or when the Ruby interpreter
38# exits, its associated temporary file is automatically deleted. This means
39# that's it's unnecessary to explicitly delete a Tempfile after use, though
40# it's good practice to do so: not explicitly deleting unused Tempfiles can
41# potentially leave behind large amounts of tempfiles on the filesystem
42# until they're garbage collected. The existence of these temp files can make
43# it harder to determine a new Tempfile filename.
44#
45# Therefore, one should always call #unlink or close in an ensure block, like
46# this:
47#
48#   file = Tempfile.new('foo')
49#   begin
50#      ...do something with file...
51#   ensure
52#      file.close
53#      file.unlink   # deletes the temp file
54#   end
55#
56# === Unlink after creation
57#
58# On POSIX systems, it's possible to unlink a file right after creating it,
59# and before closing it. This removes the filesystem entry without closing
60# the file handle, so it ensures that only the processes that already had
61# the file handle open can access the file's contents. It's strongly
62# recommended that you do this if you do not want any other processes to
63# be able to read from or write to the Tempfile, and you do not need to
64# know the Tempfile's filename either.
65#
66# For example, a practical use case for unlink-after-creation would be this:
67# you need a large byte buffer that's too large to comfortably fit in RAM,
68# e.g. when you're writing a web server and you want to buffer the client's
69# file upload data.
70#
71# Please refer to #unlink for more information and a code example.
72#
73# == Minor notes
74#
75# Tempfile's filename picking method is both thread-safe and inter-process-safe:
76# it guarantees that no other threads or processes will pick the same filename.
77#
78# Tempfile itself however may not be entirely thread-safe. If you access the
79# same Tempfile object from multiple threads then you should protect it with a
80# mutex.
81class Tempfile < DelegateClass(File)
82  include Dir::Tmpname
83
84  # call-seq:
85  #    new(basename, [tmpdir = Dir.tmpdir], [options])
86  #
87  # Creates a temporary file with permissions 0600 (= only readable and
88  # writable by the owner) and opens it with mode "w+".
89  #
90  # The +basename+ parameter is used to determine the name of the
91  # temporary file. You can either pass a String or an Array with
92  # 2 String elements. In the former form, the temporary file's base
93  # name will begin with the given string. In the latter form,
94  # the temporary file's base name will begin with the array's first
95  # element, and end with the second element. For example:
96  #
97  #   file = Tempfile.new('hello')
98  #   file.path  # => something like: "/tmp/hello2843-8392-92849382--0"
99  #
100  #   # Use the Array form to enforce an extension in the filename:
101  #   file = Tempfile.new(['hello', '.jpg'])
102  #   file.path  # => something like: "/tmp/hello2843-8392-92849382--0.jpg"
103  #
104  # The temporary file will be placed in the directory as specified
105  # by the +tmpdir+ parameter. By default, this is +Dir.tmpdir+.
106  # When $SAFE > 0 and the given +tmpdir+ is tainted, it uses
107  # '/tmp' as the temporary directory. Please note that ENV values
108  # are tainted by default, and +Dir.tmpdir+'s return value might
109  # come from environment variables (e.g. <tt>$TMPDIR</tt>).
110  #
111  #   file = Tempfile.new('hello', '/home/aisaka')
112  #   file.path  # => something like: "/home/aisaka/hello2843-8392-92849382--0"
113  #
114  # You can also pass an options hash. Under the hood, Tempfile creates
115  # the temporary file using +File.open+. These options will be passed to
116  # +File.open+. This is mostly useful for specifying encoding
117  # options, e.g.:
118  #
119  #   Tempfile.new('hello', '/home/aisaka', :encoding => 'ascii-8bit')
120  #
121  #   # You can also omit the 'tmpdir' parameter:
122  #   Tempfile.new('hello', :encoding => 'ascii-8bit')
123  #
124  # === Exceptions
125  #
126  # If Tempfile.new cannot find a unique filename within a limited
127  # number of tries, then it will raise an exception.
128  def initialize(basename, *rest)
129    if block_given?
130      warn "Tempfile.new doesn't call the given block."
131    end
132    @data = []
133    @clean_proc = Remover.new(@data)
134    ObjectSpace.define_finalizer(self, @clean_proc)
135
136    create(basename, *rest) do |tmpname, n, opts|
137      mode = File::RDWR|File::CREAT|File::EXCL
138      perm = 0600
139      if opts
140        mode |= opts.delete(:mode) || 0
141        opts[:perm] = perm
142        perm = nil
143      else
144        opts = perm
145      end
146      @data[1] = @tmpfile = File.open(tmpname, mode, opts)
147      @data[0] = @tmpname = tmpname
148      @mode = mode & ~(File::CREAT|File::EXCL)
149      perm or opts.freeze
150      @opts = opts
151    end
152
153    super(@tmpfile)
154  end
155
156  # Opens or reopens the file with mode "r+".
157  def open
158    @tmpfile.close if @tmpfile
159    @tmpfile = File.open(@tmpname, @mode, @opts)
160    @data[1] = @tmpfile
161    __setobj__(@tmpfile)
162  end
163
164  def _close    # :nodoc:
165    begin
166      @tmpfile.close if @tmpfile
167    ensure
168      @tmpfile = nil
169      @data[1] = nil if @data
170    end
171  end
172  protected :_close
173
174  # Closes the file. If +unlink_now+ is true, then the file will be unlinked
175  # (deleted) after closing. Of course, you can choose to later call #unlink
176  # if you do not unlink it now.
177  #
178  # If you don't explicitly unlink the temporary file, the removal
179  # will be delayed until the object is finalized.
180  def close(unlink_now=false)
181    if unlink_now
182      close!
183    else
184      _close
185    end
186  end
187
188  # Closes and unlinks (deletes) the file. Has the same effect as called
189  # <tt>close(true)</tt>.
190  def close!
191    _close
192    unlink
193  end
194
195  # Unlinks (deletes) the file from the filesystem. One should always unlink
196  # the file after using it, as is explained in the "Explicit close" good
197  # practice section in the Tempfile overview:
198  #
199  #   file = Tempfile.new('foo')
200  #   begin
201  #      ...do something with file...
202  #   ensure
203  #      file.close
204  #      file.unlink   # deletes the temp file
205  #   end
206  #
207  # === Unlink-before-close
208  #
209  # On POSIX systems it's possible to unlink a file before closing it. This
210  # practice is explained in detail in the Tempfile overview (section
211  # "Unlink after creation"); please refer there for more information.
212  #
213  # However, unlink-before-close may not be supported on non-POSIX operating
214  # systems. Microsoft Windows is the most notable case: unlinking a non-closed
215  # file will result in an error, which this method will silently ignore. If
216  # you want to practice unlink-before-close whenever possible, then you should
217  # write code like this:
218  #
219  #   file = Tempfile.new('foo')
220  #   file.unlink   # On Windows this silently fails.
221  #   begin
222  #      ... do something with file ...
223  #   ensure
224  #      file.close!   # Closes the file handle. If the file wasn't unlinked
225  #                    # because #unlink failed, then this method will attempt
226  #                    # to do so again.
227  #   end
228  def unlink
229    return unless @tmpname
230    begin
231      File.unlink(@tmpname)
232    rescue Errno::ENOENT
233    rescue Errno::EACCES
234      # may not be able to unlink on Windows; just ignore
235      return
236    end
237    # remove tmpname from remover
238    @data[0] = @data[1] = nil
239    @tmpname = nil
240    ObjectSpace.undefine_finalizer(self)
241  end
242  alias delete unlink
243
244  # Returns the full path name of the temporary file.
245  # This will be nil if #unlink has been called.
246  def path
247    @tmpname
248  end
249
250  # Returns the size of the temporary file.  As a side effect, the IO
251  # buffer is flushed before determining the size.
252  def size
253    if @tmpfile
254      @tmpfile.flush
255      @tmpfile.stat.size
256    elsif @tmpname
257      File.size(@tmpname)
258    else
259      0
260    end
261  end
262  alias length size
263
264  def inspect
265    "#<#{self.class}:#{path}>"
266  end
267
268  # :stopdoc:
269  class Remover
270    def initialize(data)
271      @pid = $$
272      @data = data
273    end
274
275    def call(*args)
276      return if @pid != $$
277
278      path, tmpfile = *@data
279
280      STDERR.print "removing ", path, "..." if $DEBUG
281
282      tmpfile.close if tmpfile
283
284      if path
285        begin
286          File.unlink(path)
287        rescue Errno::ENOENT
288        end
289      end
290
291      STDERR.print "done\n" if $DEBUG
292    end
293  end
294  # :startdoc:
295
296  class << self
297    # Creates a new Tempfile.
298    #
299    # If no block is given, this is a synonym for Tempfile.new.
300    #
301    # If a block is given, then a Tempfile object will be constructed,
302    # and the block is run with said object as argument. The Tempfile
303    # object will be automatically closed after the block terminates.
304    # The call returns the value of the block.
305    #
306    # In any case, all arguments (+*args+) will be passed to Tempfile.new.
307    #
308    #   Tempfile.open('foo', '/home/temp') do |f|
309    #      ... do something with f ...
310    #   end
311    #
312    #   # Equivalent:
313    #   f = Tempfile.open('foo', '/home/temp')
314    #   begin
315    #      ... do something with f ...
316    #   ensure
317    #      f.close
318    #   end
319    def open(*args)
320      tempfile = new(*args)
321
322      if block_given?
323        begin
324          yield(tempfile)
325        ensure
326          tempfile.close
327        end
328      else
329        tempfile
330      end
331    end
332  end
333end
334
335if __FILE__ == $0
336#  $DEBUG = true
337  f = Tempfile.new("foo")
338  f.print("foo\n")
339  f.close
340  f.open
341  p f.gets # => "foo\n"
342  f.close!
343end
344