Fix up Rubinius specific library specs.
[rbx.git] / lib / tempfile.rb
blobed07920df6139374f3936945ab9a7219613ec32f
2 # tempfile - manipulates temporary files
4 # $Id$
7 require 'thread'
8 require 'delegate'
9 require 'tmpdir'
11 # A class for managing temporary files.  This library is written to be
12 # thread safe.
13 class Tempfile < DelegateClass(File)
14   MAX_TRY = 10
16   class FinalizerData < Struct.new :tmpname, :tmpfile, :mutex # :nodoc:
17   end
19   # Creates a temporary file of mode 0600 in the temporary directory
20   # whose name is basename.pid.n and opens with mode "w+".  A Tempfile
21   # object works just like a File object.
22   #
23   # If tmpdir is omitted, the temporary directory is determined by
24   # Dir::tmpdir provided by 'tmpdir.rb'.
25   # When $SAFE > 0 and the given tmpdir is tainted, it uses
26   # /tmp. (Note that ENV values are tainted by default)
27   def initialize(basename, tmpdir=Dir::tmpdir)
28     if $SAFE > 0 and tmpdir.tainted?
29       tmpdir = '/tmp'
30     end
32     failure = 0
33     begin
34       @tmpname = File.join(tmpdir, make_tmpname_secure(basename))
35       @tmpfile = File.open(@tmpname, File::RDWR|File::CREAT|File::EXCL, 0600)
36     rescue Exception => e
37       failure += 1
38       retry if failure < MAX_TRY
39       raise "cannot generate tempfile `%s': #{e.message}" % @tmpname
40     end
42     @mutex = Mutex.new
44     @data = FinalizerData[@tmpname, @tmpfile, @mutex]
45     @clean_proc = Tempfile.callback(@data)
46     ObjectSpace.define_finalizer(self, @clean_proc)
48     super(@tmpfile)
50     # Now we have all the File/IO methods defined, you must not
51     # carelessly put bare puts(), etc. after this.
52   end
54   @@sequence_number = 0
55   @@sequence_mutex = Mutex.new
56   def make_tmpname_secure(basename) #:nodoc:
57     begin
58       File.open("/dev/urandom", "rb") do |random|
59         basename = "#{random.read(16).unpack('H*')}_#{basename}"
60       end
61     rescue
62     end
63     sequence_number = @@sequence_mutex.synchronize { @@sequence_number += 1 }
64     make_tmpname(basename, sequence_number)
65   end
66   private :make_tmpname_secure
68   def make_tmpname(basename, n)
69     "#{basename}.#{$$}.#{n}"
70   end
71   private :make_tmpname
73   # Opens or reopens the file with mode "r+".
74   def open
75     @mutex.synchronize do
76       @tmpfile.close if @tmpfile
77       @tmpfile = File.open(@tmpname, 'r+')
78       @data.tmpfile = @tmpfile
79       __setobj__(@tmpfile)
80     end
81   end
83   def _close    # :nodoc:
84     @tmpfile.close if @tmpfile
85     @data.tmpfile = @tmpfile = nil
86   end    
87   protected :_close
89   # Closes the file.  If the optional flag is true, unlinks the file
90   # after closing.
91   #
92   # If you don't explicitly unlink the temporary file, the removal
93   # will be delayed until the object is finalized.
94   def close(unlink_now=false)
95     if unlink_now
96       close!
97     else
98       @mutex.synchronize { _close }
99     end
100   end
102   # Closes and unlinks the file.
103   def close!
104     @mutex.synchronize do
105       _close
106       @data.mutex = nil # @clean_proc does not need to acquire the lock here
107       @clean_proc.call
108       ObjectSpace.undefine_finalizer(self)
109     end
110   end
112   # Unlinks the file.  On UNIX-like systems, it is often a good idea
113   # to unlink a temporary file immediately after creating and opening
114   # it, because it leaves other programs zero chance to access the
115   # file.
116   def unlink
117     @mutex.synchronize do
118       begin
119         begin
120           File.unlink(@tmpname)
121         rescue Errno::ENOENT
122         end
123         @data = @tmpname = nil
124         ObjectSpace.undefine_finalizer(self)
125       rescue Errno::EACCES
126         # may not be able to unlink on Windows; just ignore
127       end
128     end
129   end
130   alias delete unlink
132   # Returns the full path name of the temporary file.
133   def path
134     @mutex.synchronize { @tmpname.dup }
135   end
137   # Returns the size of the temporary file.  As a side effect, the IO
138   # buffer is flushed before determining the size.
139   def size
140     @mutex.synchronize do
141       if @tmpfile
142         @tmpfile.flush
143         @tmpfile.stat.size
144       else
145         0
146       end
147     end
148   end
149   alias length size
151   class << self
152     def callback(data)  # :nodoc:
153       pid = $$
154       lambda{
155         if pid == $$ 
156           data.mutex.lock if data.mutex
157           begin
158             print "removing ", data.tmpname, "..." if $DEBUG
160             data.tmpfile.close if data.tmpfile
162             # keep this order for thread safeness
163             begin
164               File.unlink(data.tmpname)
165             rescue Errno::ENOENT, Errno::ENOTDIR, Errno::EISDIR
166             end
168             print "done\n" if $DEBUG
169           ensure
170             data.mutex.unlock if data.mutex
171           end
172         end
173       }
174     end
176     # If no block is given, this is a synonym for new().
177     #
178     # If a block is given, it will be passed tempfile as an argument,
179     # and the tempfile will automatically be closed when the block
180     # terminates.  In this case, open() returns nil.
181     def open(*args)
182       tempfile = new(*args)
184       if block_given?
185         begin
186           yield(tempfile)
187         ensure
188           tempfile.close
189         end
191         nil
192       else
193         tempfile
194       end
195     end
196   end
199 if __FILE__ == $0
200 #  $DEBUG = true
201   f = Tempfile.new("foo")
202   f.print("foo\n")
203   f.close
204   f.open
205   p f.gets # => "foo\n"
206   f.close!