class Gem::AtomicFileWriter
Public Class Methods
# File lib/rubygems/util/atomic_file_writer.rb, line 14 def self.open(file_name) require "securerandom" unless defined?(SecureRandom) old_stat = begin File.stat(file_name) rescue SystemCallError nil end # Names can't be longer than 255B tmp_suffix = ".tmp.#{SecureRandom.hex}" dirname = File.dirname(file_name) basename = File.basename(file_name) base_slice = basename.byteslice(0, 254 - tmp_suffix.bytesize) tmp_path = File.join(dirname, ".#{base_slice}#{tmp_suffix}") # The temporary name is longer than the final one, so on Windows a # writable destination can still map to a path beyond the 260-character # MAX_PATH limit. Trim the random suffix first, keeping at least 8 hex # characters to avoid collisions, then shorten the basename if that is not # enough. Recomputing the basename from the trimmed suffix would just let # it grow back, so trim the already-sliced basename instead. if tmp_path.length >= 260 && Gem.win_platform? overflow = tmp_path.length - 259 trim = [tmp_suffix.bytesize - (".tmp.".bytesize + 8), overflow].min tmp_suffix = tmp_suffix.byteslice(0, tmp_suffix.bytesize - trim) overflow -= trim base_slice = base_slice.byteslice(0, [base_slice.bytesize - overflow, 0].max) if overflow > 0 tmp_path = File.join(dirname, ".#{base_slice}#{tmp_suffix}") end flags = File::RDWR | File::CREAT | File::EXCL | File::BINARY flags |= File::SHARE_DELETE if defined?(File::SHARE_DELETE) File.open(tmp_path, flags) do |temp_file| temp_file.binmode if old_stat # Set correct permissions on new file begin File.chown(old_stat.uid, old_stat.gid, temp_file.path) # This operation will affect filesystem ACL's File.chmod(old_stat.mode, temp_file.path) rescue Errno::EPERM, Errno::EACCES # Changing file ownership failed, moving on. end end return_val = yield temp_file rescue StandardError => error begin temp_file.close rescue StandardError nil end begin File.unlink(temp_file.path) rescue StandardError nil end raise error else begin File.rename(temp_file.path, file_name) rescue StandardError begin File.unlink(temp_file.path) rescue StandardError end raise end return_val end end
Write to a file atomically. Useful for situations where you donβt want other processes or threads to see half-written files.