From: Robert Klemme Date: 2005-11-03T00:27:10+09:00 Subject: Re: Trapping errors. 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 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