Hugh Sasse wrote:
> I see my changes to fileutils are now in the Ruby CVS.
> However, even with
>   rescue Exception, SystemCallError => e
> or
>   rescue Exception, Errno::EACCES, Errno::EBUSY => e
>
> I still cannot trap this error.  From the call stack this part of
> the code is being used, so why won't the error cause ruby to go back
> up the callstack until it finds this rescue clause?

Maybe there's another rescue clause that is closer to the place where the
exception is thrown...

    robert

>
>         Hugh
>
> On Tue, 1 Nov 2005, Hugh Sasse wrote:
>
>> On Tue, 1 Nov 2005, Robert Klemme wrote:
>>
>>> Hugh Sasse <hgs / dmu.ac.uk> wrote:
>>>> begin
>>>>  #...
>>>> rescue => e
>>>>  #...
>>>> end
>>>>
>>>> will trap e if it is a StandardError.  SystemCallErrrors are
>>>> supposed to handle Errorcodes from the OS.  All of these are
>>>> subclasses of Exception.  So why do I get this failure under
>>>> Cygwin:
>>>>
>>>> $ ruby BACKUP.RB "C:\\" "D:\\buzz_c"
>>>> cp -rp C:\ D:\buzz_c
>>>> /usr/lib/ruby/1.8/new_fileutils.rb:1251:in `initialize': Device or
>>>> resource busy - C:\/WINDOWS/WIN386.SWP (Errno::EBUSY)
>>>>        from /usr/lib/ruby/1.8/new_fileutils.rb:1251:in `copy_file'
>>>>        from /usr/lib/ruby/1.8/new_fileutils.rb:1221:in `copy'
>>>>        from /usr/lib/ruby/1.8/new_fileutils.rb:455:in `copy_entry'
>>>>        from /usr/lib/ruby/1.8/new_fileutils.rb:1314:in `traverse'
>>>>        from /usr/lib/ruby/1.8/new_fileutils.rb:453:in `copy_entry'
>>>>        from /usr/lib/ruby/1.8/new_fileutils.rb:424:in `cp_r'
>>>>        from /usr/lib/ruby/1.8/new_fileutils.rb:1385:in
>>>>        `fu_each_src_dest' from
>>>>        /usr/lib/ruby/1.8/new_fileutils.rb:1401:in
>>>>        `fu_each_src_dest0' from
>>>>        /usr/lib/ruby/1.8/new_fileutils.rb:1383:in
>>>> `fu_each_src_dest' from /usr/lib/ruby/1.8/new_fileutils.rb:422:in
>>>> `cp_r' from BACKUP.RB:27
>>>>
>>>> hgs@buzz ~/downloads
>>>>
>>>> when my modified FileUtils.cp_r has
>>>>
>>>> begin
>>>>  copy_entry ...
>>>> rescue Exception => e
>>>>  logger.error("backup"){"Error was #{e}")
>>>> end
>>>>
>>>> (essentially.  Theres a bit more to it than that, but the details
>>>> shouldn't matter for my question.)  So why can't I rescue it?  (I'm
>>>> trying to log, and skip files I can't backup so at least I get most
>>>> of the files, and know which ones I have not.)
>>>
>>> Maybe it's in another thread.  Or your code is actually not between
>>> "begin" and "rescue" but outside of that.
>>
>> there's no threading in there, and I'm pretty certain it is within
>> that, because it is in the call to copy_entry
>>
>>>
>>> Kind regards
>>>
>>>    robert
>>>
>>
>> The modified fileutils is (heavily pruned) below
>> I've changed cp_r.
>>
>>         Hugh
>>
>> #
>> # = fileutils.rb
>> #
>> # Copyright (c) 2000-2005 Minero Aoki <aamine / loveruby.net>
>> #
>> # This program is free software.
>> # You can distribute/modify this program under the same terms of
>> ruby. #
>> # == module FileUtils
>> #
>> # Namespace for several file utility methods for copying, moving,
>> removing, etc. #
>> # === Module Functions
>> #
>> #       [...]
>> #   cp_r(src, dest, options) {|s,d,e|...}
>> #   cp_r(list, dir, options) {|s,d,e|...}
>> #       [...]
>> #
>> # The <tt>options</tt> parameter is a hash of options, taken from
>> the list # <tt>:force</tt>, <tt>:noop</tt>, <tt>:preserve</tt>, and
>> <tt>:verbose</tt>. # <tt>:noop</tt> means that no changes are made.
>> The other two are obvious. # Each method documents the options that
>> it honours. #
>> # All methods that have the concept of a "source" file or directory
>> can take # either one file or a list of files in that argument.  See
>> the method # documentation for examples.
>> #
>> # There are some `low level' methods, which do not accept any option:
>> #
>> #   copy_entry(src, dest, preserve = false, dereference = false)
>> #   copy_file(src, dest, preserve = false, dereference = true)
>> #       [...]
>> #
>> # == module FileUtils::Verbose
>> #
>> # This module has all methods of FileUtils module, but it outputs
>> messages # before acting.  This equates to passing the
>> <tt>:verbose</tt> flag to methods # in FileUtils.
>> #
>> # == module FileUtils::NoWrite
>> #
>> # This module has all methods of FileUtils module, but never changes
>> # files/directories.  This equates to passing the <tt>:noop</tt>
>> flag to methods # in FileUtils.
>> #
>> # == module FileUtils::DryRun
>> #
>> # This module has all methods of FileUtils module, but never changes
>> # files/directories.  This equates to passing the <tt>:noop</tt> and
>> # <tt>:verbose</tt> flags to methods in FileUtils.
>> #
>>
>> module FileUtils
>>
>>   def self.private_module_function(name)   #:nodoc:
>>     module_function name
>>     private_class_method name
>>   end
>>
>>   # This hash table holds command options.
>>   OPT_TABLE = {}   #:nodoc: internal use only
>>
>> #       [...]
>>
>>   def fu_mkdir(path, mode)   #:nodoc:
>>     path = path.sub(%r</\z>, '')
>>     if mode
>>       Dir.mkdir path, mode
>>       File.chmod mode, path
>>     else
>>       Dir.mkdir path
>>     end
>>   end
>>   private_module_function :fu_mkdir
>>
>> #       [...]
>>
>>   #
>>   # Options: preserve noop verbose dereference_root
>>   #
>>   # Copies +src+ to +dest+. If +src+ is a directory, this method
>>   copies # all its contents recursively. If +dest+ is a directory,
>>   copies # +src+ to +dest/src+.
>>   #
>>   # +src+ can be a list of files.
>>   #
>>   #   # Installing ruby library "mylib" under the site_ruby
>>   #   FileUtils.rm_r site_ruby + '/mylib', :force
>>   #   FileUtils.cp_r 'lib/', site_ruby + '/mylib'
>>   #
>>   #   # Examples of copying several files to target directory.
>>   #   FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby +
>>   '/tmail' #   FileUtils.cp_r Dir.glob('*.rb'),
>>   '/home/aamine/lib/ruby', :noop => true, :verbose => true #
>>   #   # If you want to copy all contents of a directory instead of
>>   the #   # directory itself, c.f. src/x -> dest/x, src/y -> dest/y,
>>   #   # use following code.
>>   #   FileUtils.cp_r 'src/.', 'dest'     # cp_r('src', 'dest') makes
>>   src/dest, #                                      # but this
>>   doesn't. #
>>   def cp_r(src, dest, options = {})
>>     fu_check_options options, :preserve, :noop, :verbose,
>>     :dereference_root fu_output_message "cp -r#{options[:preserve] ?
>>     'p' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
>>     return if options[:noop] options[:dereference_root] = true
>>     unless options.key?(:dereference_root) fu_each_src_dest(src,
>>       dest) do |s, d| begin
>>         copy_entry s, d, options[:preserve],
>>       options[:dereference_root] rescue Exception => e
>>         stop = true
>>         if block_given?
>>           stop = yield s,d,e
>>         end
>>         raise if stop
>>       end
>>     end
>>   end
>>   module_function :cp_r
>>
>>   OPT_TABLE['cp_r'] = %w( noop verbose preserve dereference_root )
>>
>>   #
>>   # Copies a file system entry +src+ to +dest+.
>>   # If +src+ is a directory, this method copies its contents
>>   recursively. # This method preserves file types, c.f. symlink,
>>   directory... # (FIFO, device files and etc. are not supported yet)
>>   #
>>   # Both of +src+ and +dest+ must be a path name.
>>   # +src+ must exist, +dest+ must not exist.
>>   #
>>   # If +preserve+ is true, this method preserves owner, group,
>>   permissions # and modified time.
>>   #
>>   # If +dereference_root+ is true, this method dereference tree root.
>>   #
>>   def copy_entry(src, dest, preserve = false, dereference_root =
>>     false) Entry_.new(src, nil, dereference_root).traverse do |ent|
>>       destent = Entry_.new(dest, ent.rel, false)
>>       ent.copy destent.path
>>       ent.copy_metadata destent.path if preserve
>>     end
>>   end
>>   module_function :copy_entry
>>
>>   #
>>   # Copies file contents of +src+ to +dest+.
>>   # Both of +src+ and +dest+ must be a path name.
>>   #
>>   def copy_file(src, dest, preserve = false, dereference = true)
>>     ent = Entry_.new(src, nil, dereference)
>>     ent.copy_file dest
>>     ent.copy_metadata dest if preserve
>>   end
>>   module_function :copy_file
>>
>> #       [...]
>>
>>
>>   class Entry_   #:nodoc: internal use only
>>     include StreamUtils_
>>
>>     def initialize(a, b = nil, deref = false)
>>       @prefix = @rel = @path = nil
>>       if b
>>         @prefix = a
>>         @rel = b
>>       else
>>         @path = a
>>       end
>>       @deref = deref
>>       @stat = nil
>>       @lstat = nil
>>     end
>>
>>     def inspect
>>       "\#<#{self.class} #{path()}>"
>>     end
>>
>>     def path
>>       if @path
>>         @path.to_str
>>       else
>>         join(@prefix, @rel)
>>       end
>>     end
>>
>>     def prefix
>>       @prefix || @path
>>     end
>>
>>     def rel
>>       @rel
>>     end
>>
>>     def dereference?
>>       @deref
>>     end
>>
>>     def exist?
>>       lstat! ? true : false
>>     end
>>
>>     def file?
>>       s = lstat!
>>       s and s.file?
>>     end
>>
>>     def directory?
>>       s = lstat!
>>       s and s.directory?
>>     end
>>
>>     def symlink?
>>       s = lstat!
>>       s and s.symlink?
>>     end
>>
>>     def chardev?
>>       s = lstat!
>>       s and s.chardev?
>>     end
>>
>>     def blockdev?
>>       s = lstat!
>>       s and s.blockdev?
>>     end
>>
>>     def socket?
>>       s = lstat!
>>       s and s.socket?
>>     end
>>
>>     def pipe?
>>       s = lstat!
>>       s and s.pipe?
>>     end
>>
>>     S_IF_DOOR = 0xD000
>>
>>     def door?
>>       s = lstat!
>>       s and (s.mode & 0xF000 == S_IF_DOOR)
>>     end
>>
>>     def entries
>>       Dir.entries(path())\
>>           .reject {|n| n == '.' or n == '..' }\
>>           .map {|n| Entry_.new(prefix(), join(rel(), n.untaint)) }
>>     end
>>
>>     def stat
>>       return @stat if @stat
>>       if lstat() and lstat().symlink?
>>         @stat = File.stat(path())
>>       else
>>         @stat = lstat()
>>       end
>>       @stat
>>     end
>>
>>     def stat!
>>       return @stat if @stat
>>       if lstat! and lstat!.symlink?
>>         @stat = File.stat(path())
>>       else
>>         @stat = lstat!
>>       end
>>       @stat
>>     rescue SystemCallError
>>       nil
>>     end
>>
>>     def lstat
>>       if dereference?
>>         @lstat ||= File.stat(path())
>>       else
>>         @lstat ||= File.lstat(path())
>>       end
>>     end
>>
>>     def lstat!
>>       lstat()
>>     rescue SystemCallError
>>       nil
>>     end
>>
>>     def chmod(mode)
>>       if symlink?
>>         File.lchmod mode, path() if have_lchmod?
>>       else
>>         File.chmod mode, path()
>>       end
>>     end
>>
>>     def chown(uid, gid)
>>       if symlink?
>>         File.lchown uid, gid, path() if have_lchown?
>>       else
>>         File.chown uid, gid, path()
>>       end
>>     end
>>
>>     def copy(dest)
>>       case
>>       when file?
>>         copy_file dest
>>       when directory?
>>         begin
>>           Dir.mkdir dest
>>         rescue
>>           raise unless File.directory?(dest)
>>         end
>>       when symlink?
>>         File.symlink File.readlink(path()), dest
>>       when chardev?
>>         raise "cannot handle device file" unless
>>         File.respond_to?(:mknod) mknod dest, ?c, 0666, lstat().rdev
>>       when blockdev?
>>         raise "cannot handle device file" unless
>>         File.respond_to?(:mknod) mknod dest, ?b, 0666, lstat().rdev
>>       when socket?
>>         raise "cannot handle socket" unless File.respond_to?(:mknod)
>>         mknod dest, nil, lstat().mode, 0
>>       when pipe?
>>         raise "cannot handle FIFO" unless File.respond_to?(:mkfifo)
>>         mkfifo dest, 0666
>>       when door?
>>         raise "cannot handle door: #{path()}"
>>       else
>>         raise "unknown file type: #{path()}"
>>       end
>>     end
>>
>>     def copy_file(dest)
>>       st = stat()
>>       File.open(path(),  'rb') {|r|
>>         File.open(dest, 'wb', st.mode) {|w|
>>           fu_copy_stream0 r, w, (fu_blksize(st) ||
>>         fu_default_blksize()) }
>>       }
>>     end
>>
>>     def copy_metadata(path)
>>       st = lstat()
>>       File.utime st.atime, st.mtime, path
>>       begin
>>         File.chown st.uid, st.gid, path
>>       rescue Errno::EPERM
>>         # clear setuid/setgid
>>         File.chmod st.mode & 01777, path
>>       else
>>         File.chmod st.mode, path
>>       end
>>     end
>>
>> #       [...]
>>
>>     def platform_support
>>       return yield unless fu_windows?
>>       first_time_p = true
>>       begin
>>         yield
>>       rescue Errno::ENOENT
>>         raise
>>       rescue => err
>>         if first_time_p
>>           first_time_p = false
>>           begin
>>             File.chmod 0700, path()   # Windows does not have symlink
>>             retry
>>           rescue SystemCallError
>>           end
>>         end
>>         raise err
>>       end
>>     end
>>
>>     def preorder_traverse
>>       stack = [self]
>>       while ent = stack.pop
>>         yield ent
>>         stack.concat ent.entries.reverse if ent.directory?
>>       end
>>     end
>>
>>     alias traverse preorder_traverse
>>
>>     def postorder_traverse
>>       if directory?
>>         entries().each do |ent|
>>           ent.postorder_traverse do |e|
>>             yield e
>>           end
>>         end
>>       end
>>       yield self
>>     end
>>
>>
>> #       [...]
>>
>>     def join(dir, base)
>>       return dir.to_str if not base or base == '.'
>>       return base.to_str if not dir or dir == '.'
>>       File.join(dir, base)
>>     end
>>   end   # class Entry_
>>
>>   def fu_list(arg)   #:nodoc:
>>     [arg].flatten.map {|path| path.to_str }
>>   end
>>   private_module_function :fu_list
>>
>>   def fu_each_src_dest(src, dest)   #:nodoc:
>>     fu_each_src_dest0(src, dest) do |s, d|
>>       raise ArgumentError, "same file: #{s} and #{d}" if fu_same?(s,
>>       d) yield s, d
>>     end
>>   end
>>   private_module_function :fu_each_src_dest
>>
>>   def fu_each_src_dest0(src, dest)   #:nodoc:
>>     if src.is_a?(Array)
>>       src.each do |s|
>>         s = s.to_str
>>         yield s, File.join(dest, File.basename(s))
>>       end
>>     else
>>       src = src.to_str
>>       if File.directory?(dest)
>>         yield src, File.join(dest, File.basename(src))
>>       else
>>         yield src, dest.to_str
>>       end
>>     end
>>   end
>>   private_module_function :fu_each_src_dest0
>>
>>   def fu_same?(a, b)   #:nodoc:
>>     if fu_have_st_ino?
>>       st1 = File.stat(a)
>>       st2 = File.stat(b)
>>       st1.dev == st2.dev and st1.ino == st2.ino
>>     else
>>       File.expand_path(a) == File.expand_path(b)
>>     end
>>   rescue Errno::ENOENT
>>     return false
>>   end
>>   private_module_function :fu_same?
>>
>>   def fu_have_st_ino?   #:nodoc:
>>     not fu_windows?
>>   end
>>   private_module_function :fu_have_st_ino?
>>
>>   def fu_check_options(options, *optdecl)   #:nodoc:
>>     h = options.dup
>>     optdecl.each do |name|
>>       h.delete name
>>     end
>>     raise ArgumentError, "no such option: #{h.keys.join(' ')}"
>>   unless h.empty? end
>>   private_module_function :fu_check_options
>>
>>   def fu_update_option(args, new)   #:nodoc:
>>     if args.last.is_a?(Hash)
>>       args[-1] = args.last.dup.update(new)
>>     else
>>       args.push new
>>     end
>>     args
>>   end
>>   private_module_function :fu_update_option
>>
>>   @fileutils_output = $stderr
>>   @fileutils_label  = ''
>>
>>   def fu_output_message(msg)   #:nodoc:
>>     @fileutils_output ||= $stderr
>>     @fileutils_label  ||= ''
>>     @fileutils_output.puts @fileutils_label + msg
>>   end
>>   private_module_function :fu_output_message
>>
>>   #
>>   # Returns an Array of method names which have any options.
>>   #
>>   #   p FileUtils.commands  #=> ["chmod", "cp", "cp_r", "install",
>>   ...] #
>>   def FileUtils.commands
>>     OPT_TABLE.keys
>>   end
>>
>>   #
>>   # Returns an Array of option names.
>>   #
>>   #   p FileUtils.options  #=> ["noop", "force", "verbose",
>>   "preserve", "mode"] #
>>   def FileUtils.options
>>     OPT_TABLE.values.flatten.uniq
>>   end
>>
>>   #
>>   # Returns true if the method +mid+ have an option +opt+.
>>   #
>>   #   p FileUtils.have_option?(:cp, :noop)     #=> true
>>   #   p FileUtils.have_option?(:rm, :force)    #=> true
>>   #   p FileUtils.have_option?(:rm, :perserve) #=> false
>>   #
>>   def FileUtils.have_option?(mid, opt)
>>     li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such
>>     method: #{mid}" li.include?(opt.to_s)
>>   end
>>
>>   #
>>   # Returns an Array of option names of the method +mid+.
>>   #
>>   #   p FileUtils.options(:rm)  #=> ["noop", "verbose", "force"]
>>   #
>>   def FileUtils.options_of(mid)
>>     OPT_TABLE[mid.to_s]
>>   end
>>
>> #       [...]
>>
>> end