From: Hugh Sasse Date: 2005-11-03T01:10:36+09:00 Subject: Re: Trapping errors. 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 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 > >> # > >> # 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 options parameter is a hash of options, taken from > >> the list # :force, :noop, :preserve, and > >> :verbose. # :noop 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 > >> :verbose 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 :noop > >> 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 :noop and > >> # :verbose 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, '') > >> 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 > > >