1# frozen_string_literal: true 2# 3# tempfile - manipulates temporary files 4# 5# $Id: tempfile.rb 66415 2018-12-16 12:09:08Z nobu $ 6# 7 8require 'delegate' 9require 'tmpdir' 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 # Creates a temporary file with permissions 0600 (= only readable and 83 # writable by the owner) and opens it with mode "w+". 84 # 85 # The +basename+ parameter is used to determine the name of the 86 # temporary file. You can either pass a String or an Array with 87 # 2 String elements. In the former form, the temporary file's base 88 # name will begin with the given string. In the latter form, 89 # the temporary file's base name will begin with the array's first 90 # element, and end with the second element. For example: 91 # 92 # file = Tempfile.new('hello') 93 # file.path # => something like: "/tmp/hello2843-8392-92849382--0" 94 # 95 # # Use the Array form to enforce an extension in the filename: 96 # file = Tempfile.new(['hello', '.jpg']) 97 # file.path # => something like: "/tmp/hello2843-8392-92849382--0.jpg" 98 # 99 # The temporary file will be placed in the directory as specified 100 # by the +tmpdir+ parameter. By default, this is +Dir.tmpdir+. 101 # When $SAFE > 0 and the given +tmpdir+ is tainted, it uses 102 # '/tmp' as the temporary directory. Please note that ENV values 103 # are tainted by default, and +Dir.tmpdir+'s return value might 104 # come from environment variables (e.g. <tt>$TMPDIR</tt>). 105 # 106 # file = Tempfile.new('hello', '/home/aisaka') 107 # file.path # => something like: "/home/aisaka/hello2843-8392-92849382--0" 108 # 109 # You can also pass an options hash. Under the hood, Tempfile creates 110 # the temporary file using +File.open+. These options will be passed to 111 # +File.open+. This is mostly useful for specifying encoding 112 # options, e.g.: 113 # 114 # Tempfile.new('hello', '/home/aisaka', encoding: 'ascii-8bit') 115 # 116 # # You can also omit the 'tmpdir' parameter: 117 # Tempfile.new('hello', encoding: 'ascii-8bit') 118 # 119 # Note: +mode+ keyword argument, as accepted by Tempfile, can only be 120 # numeric, combination of the modes defined in File::Constants. 121 # 122 # === Exceptions 123 # 124 # If Tempfile.new cannot find a unique filename within a limited 125 # number of tries, then it will raise an exception. 126 def initialize(basename="", tmpdir=nil, mode: 0, **options) 127 warn "Tempfile.new doesn't call the given block.", uplevel: 1 if block_given? 128 129 @unlinked = false 130 @mode = mode|File::RDWR|File::CREAT|File::EXCL 131 ::Dir::Tmpname.create(basename, tmpdir, options) do |tmpname, n, opts| 132 opts[:perm] = 0600 133 @tmpfile = File.open(tmpname, @mode, opts) 134 @opts = opts.freeze 135 end 136 ObjectSpace.define_finalizer(self, Remover.new(@tmpfile)) 137 138 super(@tmpfile) 139 end 140 141 # Opens or reopens the file with mode "r+". 142 def open 143 _close 144 mode = @mode & ~(File::CREAT|File::EXCL) 145 @tmpfile = File.open(@tmpfile.path, mode, @opts) 146 __setobj__(@tmpfile) 147 end 148 149 def _close # :nodoc: 150 @tmpfile.close 151 end 152 protected :_close 153 154 # Closes the file. If +unlink_now+ is true, then the file will be unlinked 155 # (deleted) after closing. Of course, you can choose to later call #unlink 156 # if you do not unlink it now. 157 # 158 # If you don't explicitly unlink the temporary file, the removal 159 # will be delayed until the object is finalized. 160 def close(unlink_now=false) 161 _close 162 unlink if unlink_now 163 end 164 165 # Closes and unlinks (deletes) the file. Has the same effect as called 166 # <tt>close(true)</tt>. 167 def close! 168 close(true) 169 end 170 171 # Unlinks (deletes) the file from the filesystem. One should always unlink 172 # the file after using it, as is explained in the "Explicit close" good 173 # practice section in the Tempfile overview: 174 # 175 # file = Tempfile.new('foo') 176 # begin 177 # # ...do something with file... 178 # ensure 179 # file.close 180 # file.unlink # deletes the temp file 181 # end 182 # 183 # === Unlink-before-close 184 # 185 # On POSIX systems it's possible to unlink a file before closing it. This 186 # practice is explained in detail in the Tempfile overview (section 187 # "Unlink after creation"); please refer there for more information. 188 # 189 # However, unlink-before-close may not be supported on non-POSIX operating 190 # systems. Microsoft Windows is the most notable case: unlinking a non-closed 191 # file will result in an error, which this method will silently ignore. If 192 # you want to practice unlink-before-close whenever possible, then you should 193 # write code like this: 194 # 195 # file = Tempfile.new('foo') 196 # file.unlink # On Windows this silently fails. 197 # begin 198 # # ... do something with file ... 199 # ensure 200 # file.close! # Closes the file handle. If the file wasn't unlinked 201 # # because #unlink failed, then this method will attempt 202 # # to do so again. 203 # end 204 def unlink 205 return if @unlinked 206 begin 207 File.unlink(@tmpfile.path) 208 rescue Errno::ENOENT 209 rescue Errno::EACCES 210 # may not be able to unlink on Windows; just ignore 211 return 212 end 213 ObjectSpace.undefine_finalizer(self) 214 @unlinked = true 215 end 216 alias delete unlink 217 218 # Returns the full path name of the temporary file. 219 # This will be nil if #unlink has been called. 220 def path 221 @unlinked ? nil : @tmpfile.path 222 end 223 224 # Returns the size of the temporary file. As a side effect, the IO 225 # buffer is flushed before determining the size. 226 def size 227 if !@tmpfile.closed? 228 @tmpfile.size # File#size calls rb_io_flush_raw() 229 else 230 File.size(@tmpfile.path) 231 end 232 end 233 alias length size 234 235 # :stopdoc: 236 def inspect 237 if closed? 238 "#<#{self.class}:#{path} (closed)>" 239 else 240 "#<#{self.class}:#{path}>" 241 end 242 end 243 244 class Remover # :nodoc: 245 def initialize(tmpfile) 246 @pid = Process.pid 247 @tmpfile = tmpfile 248 end 249 250 def call(*args) 251 return if @pid != Process.pid 252 253 $stderr.puts "removing #{@tmpfile.path}..." if $DEBUG 254 255 @tmpfile.close 256 begin 257 File.unlink(@tmpfile.path) 258 rescue Errno::ENOENT 259 end 260 261 $stderr.puts "done" if $DEBUG 262 end 263 end 264 265 class << self 266 # :startdoc: 267 268 # Creates a new Tempfile. 269 # 270 # If no block is given, this is a synonym for Tempfile.new. 271 # 272 # If a block is given, then a Tempfile object will be constructed, 273 # and the block is run with said object as argument. The Tempfile 274 # object will be automatically closed after the block terminates. 275 # The call returns the value of the block. 276 # 277 # In any case, all arguments (<code>*args</code>) will be passed to Tempfile.new. 278 # 279 # Tempfile.open('foo', '/home/temp') do |f| 280 # # ... do something with f ... 281 # end 282 # 283 # # Equivalent: 284 # f = Tempfile.open('foo', '/home/temp') 285 # begin 286 # # ... do something with f ... 287 # ensure 288 # f.close 289 # end 290 def open(*args) 291 tempfile = new(*args) 292 293 if block_given? 294 begin 295 yield(tempfile) 296 ensure 297 tempfile.close 298 end 299 else 300 tempfile 301 end 302 end 303 end 304end 305 306# Creates a temporary file as usual File object (not Tempfile). 307# It doesn't use finalizer and delegation. 308# 309# If no block is given, this is similar to Tempfile.new except 310# creating File instead of Tempfile. 311# The created file is not removed automatically. 312# You should use File.unlink to remove it. 313# 314# If a block is given, then a File object will be constructed, 315# and the block is invoked with the object as the argument. 316# The File object will be automatically closed and 317# the temporary file is removed after the block terminates. 318# The call returns the value of the block. 319# 320# In any case, all arguments (+basename+, +tmpdir+, +mode+, and 321# <code>**options</code>) will be treated as Tempfile.new. 322# 323# Tempfile.create('foo', '/home/temp') do |f| 324# # ... do something with f ... 325# end 326# 327def Tempfile.create(basename="", tmpdir=nil, mode: 0, **options) 328 tmpfile = nil 329 Dir::Tmpname.create(basename, tmpdir, options) do |tmpname, n, opts| 330 mode |= File::RDWR|File::CREAT|File::EXCL 331 opts[:perm] = 0600 332 tmpfile = File.open(tmpname, mode, opts) 333 end 334 if block_given? 335 begin 336 yield tmpfile 337 ensure 338 unless tmpfile.closed? 339 if File.identical?(tmpfile, tmpfile.path) 340 unlinked = File.unlink tmpfile.path rescue nil 341 end 342 tmpfile.close 343 end 344 unless unlinked 345 begin 346 File.unlink tmpfile.path 347 rescue Errno::ENOENT 348 end 349 end 350 end 351 else 352 tmpfile 353 end 354end 355