From: nobu.nokada@... Date: 2005-04-02T21:33:52+09:00 Subject: Re: creating a temporary directory Hi, At Sat, 2 Apr 2005 18:26:51 +0900, Wybo Dekker wrote in [ruby-talk:136465]: > I would like to create a temporary directory that disappears > after exit (analogous to Tempfile). But ruby doesn't seem to have a > Tempdir class? Once I had proposed it at [ruby-dev:21336], and this is the patch for latest version. Index: lib/tempfile.rb =================================================================== RCS file: /cvs/ruby/src/ruby/lib/tempfile.rb,v retrieving revision 1.27 diff -U2 -p -r1.27 tempfile.rb --- lib/tempfile.rb 15 Nov 2004 16:45:02 -0000 1.27 +++ lib/tempfile.rb 16 Nov 2004 01:31:27 -0000 @@ -8,19 +8,16 @@ require 'delegate' require 'tmpdir' -# A class for managing temporary files. This library is written to be -# thread safe. -class Tempfile < DelegateClass(File) +module AutoRemoval MAX_TRY = 10 @@cleanlist = [] - # Creates a temporary file of mode 0600 in the temporary directory - # whose name is basename.pid.n and opens with mode "w+". A Tempfile - # object works just like a File object. - # - # If tmpdir is omitted, the temporary directory is determined by - # Dir::tmpdir provided by 'tmpdir.rb'. - # When $SAFE > 0 and the given tmpdir is tainted, it uses - # /tmp. (Note that ENV values are tainted by default) - def initialize(basename, tmpdir=Dir::tmpdir) + private + + def make_tmpname(basename, n) + sprintf('%s%d.%d', basename, $$, n) + end + private :make_tmpname + + def createtmp(basename, tmpdir=Dir::tmpdir) # :nodoc: if $SAFE > 0 and tmpdir.tainted? tmpdir = '/tmp' @@ -29,17 +26,13 @@ class Tempfile < DelegateClass(File) lock = nil n = failure = 0 - + begin Thread.critical = true - begin - tmpname = File.join(tmpdir, make_tmpname(basename, n)) - lock = tmpname + '.lock' - n += 1 - end while @@cleanlist.include?(tmpname) or - File.exist?(lock) or File.exist?(tmpname) - - Dir.mkdir(lock) + tmpname = File.join(tmpdir, make_tmpname(basename, n)) + n += 1 + end until !@@cleanlist.include?(tmpname) and yield(tmpname) rescue + p $! failure += 1 retry if failure < MAX_TRY @@ -49,13 +42,63 @@ class Tempfile < DelegateClass(File) end - @data = [tmpname] - @clean_proc = Tempfile.callback(@data) + tmpname + end + + def self.callback(path, clear) # :nodoc: + @@cleanlist << path + data = [path] + pid = $$ + return Proc.new { + if pid == $$ + path, tmpfile = *data + + print "removing ", path, "..." if $DEBUG + + tmpfile.close if tmpfile + + # keep this order for thread safeness + if File.exist?(path) + clear.call(path) + end + @@cleanlist.delete(path) + + print "done\n" if $DEBUG + end + }, data + end + + def self.unregister(path) + @@cleanlist.delete(path) + end +end + +# A class for managing temporary files. This library is written to be +# thread safe. +class Tempfile < DelegateClass(File) + include AutoRemoval + + # Creates a temporary file of mode 0600 in the temporary directory + # whose name is basename.pid.n and opens with mode "w+". A Tempfile + # object works just like a File object. + # + # If tmpdir is omitted, the temporary directory is determined by + # Dir::tmpdir provided by 'tmpdir.rb'. + # When $SAFE > 0 and the given tmpdir is tainted, it uses + # /tmp. (Note that ENV values are tainted by default) + def initialize(*args) + lock = nil + tmpname = createtmp(*args) do |tmpname| + lock = tmpname + '.lock' + unless File.exist?(lock) or File.exist?(tmpname) + Dir.mkdir(lock) + end + end + + @clean_proc, @data = AutoRemoval.callback(tmpname, File.method(:unlink)) ObjectSpace.define_finalizer(self, @clean_proc) @tmpfile = File.open(tmpname, File::RDWR|File::CREAT|File::EXCL, 0600) @tmpname = tmpname - @@cleanlist << @tmpname @data[1] = @tmpfile - @data[2] = @@cleanlist super(@tmpfile) @@ -67,9 +110,4 @@ class Tempfile < DelegateClass(File) end - def make_tmpname(basename, n) - sprintf('%s%d.%d', basename, $$, n) - end - private :make_tmpname - # Opens or reopens the file with mode "r+". def open @@ -87,5 +125,5 @@ class Tempfile < DelegateClass(File) protected :_close - #Closes the file. If the optional flag is true, unlinks the file + # Closes the file. If the optional flag is true, unlinks the file # after closing. # @@ -116,5 +154,5 @@ class Tempfile < DelegateClass(File) begin File.unlink(@tmpname) if File.exist?(@tmpname) - @@cleanlist.delete(@tmpname) + AutoRemoval.unregister(@tmpname) @data = @tmpname = nil ObjectSpace.undefine_finalizer(self) @@ -143,23 +181,4 @@ class Tempfile < DelegateClass(File) class << self - def callback(data) # :nodoc: - pid = $$ - Proc.new { - if pid == $$ - path, tmpfile, cleanlist = *data - - print "removing ", path, "..." if $DEBUG - - tmpfile.close if tmpfile - - # keep this order for thread safeness - File.unlink(path) if File.exist?(path) - cleanlist.delete(path) if cleanlist - - print "done\n" if $DEBUG - end - } - end - # If no block is given, this is a synonym for new(). # @@ -185,4 +204,33 @@ class Tempfile < DelegateClass(File) end +require 'pathname' +class Tempdir < Pathname + include AutoRemoval + + def initialize(*args) + require 'fileutils' + + tmpname = createtmp(*args) do |tmpname| + unless File.exist?(tmpname) + Dir.mkdir(tmpname, 0700) + end + end + + super(tmpname) + @clean_proc, = AutoRemoval.callback(tmpname, FileUtils.method(:rm_rf)) + ObjectSpace.define_finalizer(self, @clean_proc) + end + + def open(basename, *modes, &block) + File.open(self+basename, *modes, &block) + end + + def clear + FileUtils.rm_rf(@tmpname) + @clean_proc.call + ObjectSpace.undefine_finalizer(self) + end +end + if __FILE__ == $0 # $DEBUG = true @@ -193,3 +241,6 @@ if __FILE__ == $0 p f.gets # => "foo\n" f.close! + d = Tempdir.new("hoge") + system("ls", "-dl", d.to_s) + p d end -- Nobu Nakada