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