On Thu, 3 Nov 2005, Robert Klemme wrote:

> 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...

But unless I have completely misunderstood the point of rescue, even
if that calls raise, the exception will still be caught by my
enclosing rescue.

neelix hgs 15 %> irb
irb(main):001:0> begin
irb(main):002:1*   begin
irb(main):003:2*     raise StandardError
irb(main):004:2>   rescue
irb(main):005:2>     raise
irb(main):006:2>   end
irb(main):007:1> rescue
irb(main):008:1>   puts "caught here"
irb(main):009:1> end
caught here
=> nil
irb(main):010:0>

So, if I'm doing
  rescue Exception => e 
how can Errno::EACCES or Errno::EBUSY get past that and crash out?
> 
>     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
> 
> 
>