Trapping errors.

H

Hugh Sasse

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

Anyone also reading ruby-core will realize that my patch didn't work
in practice.

Thank you,
Hugh
 
R

Robert Klemme

Hugh Sasse said:
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.

Kind regards

robert
 
H

Hugh Sasse

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 <[email protected]>
#
# 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
 
H

Hugh Sasse

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?

Hugh

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 <[email protected]>
#
# 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
 
R

Robert Klemme

Hugh said:
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

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 <[email protected]>
#
# 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
 
H

Hugh Sasse

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, Robert Klemme 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 <[email protected]>
#
# 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
 
R

Robert Klemme

Hugh said:
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.

But who guarantees that the other rescue clause actually throws again? If
it doesn't you can't catch it.

....
rescue Exception => e
puts "Catch me if you can >:-}"
end

;-)

Cheers

robert
 
H

Hugh Sasse

But who guarantees that the other rescue clause actually throws again? If
it doesn't you can't catch it.

....
rescue Exception => e
puts "Catch me if you can >:-}"
end

;-)

Then it's already caught, and I should never see the error.

neelix hgs 58 %> irb
irb(main):001:0> begin
irb(main):002:1* begin
irb(main):003:2* raise StandardError
irb(main):004:2> rescue => e
irb(main):005:2> puts "catch me if you can #{e}"
irb(main):006:2> end
irb(main):007:1> rescue
irb(main):008:1> puts "caught something"
irb(main):009:1> end
catch me if you can StandardError
=> nil
irb(main):010:0>

i.e. that's a normal exit.

So, I have an error that is not already caught, and I can't catch
it.
Cheers

robert

Hugh
 
R

Robert Klemme

Hugh Sasse said:
Then it's already caught, and I should never see the error.

Why not? If it's printed in the other rescue clause you would see it. Or
am I missing something here?

robert
 
H

Hugh Sasse

Why not? If it's printed in the other rescue clause you would see it. Or am
I missing something here?

I wouldn't see it as an error which kills my program. It would have
been caught, and dealt with, or caught and re-raised, in which case
the outer block would catch it.

1 We have established that a begin...rescue...end block
will rescue any errors raised inside it, provided the rescue
clause matches that error type.

2 We have established that if a begin ... rescue ... raise ... end
block re-raises an error, it can be caught by a surrounding
begin...rescue...end block

3 My case is that I have a
begin
part1
rescue Exception, SystemCallError, Errno::EACCES, Errno::EBUSY => e
part2
end

block and it is not catching a Errno::EBUSY error from part1: The
error crashes the program. So:
If that error has been raised in part1, and not caught in part1
then I should be able to catch it.
If that error has been raised in part1, and caught in part1,
part2 should never come into play, and the program should
continue

4 How can a subclass of Exception not be caught by Exception?
5 Why doesn't SystemCallError catch it?
6 Why doesn't the Errno::%s form catch it?

i.e everything between "Exception" and " =>" should be unnecessary,
but even with those it doesn't catch the error.
Hugh
 
R

Robert Klemme

Hugh said:
I wouldn't see it as an error which kills my program. It would have
been caught, and dealt with, or caught and re-raised, in which case
the outer block would catch it.

Well, there's at least the theoretical chance that this block just calls
#exit... :)
1 We have established that a begin...rescue...end block
will rescue any errors raised inside it, provided the rescue
clause matches that error type.

2 We have established that if a begin ... rescue ... raise ... end
block re-raises an error, it can be caught by a surrounding
begin...rescue...end block

3 My case is that I have a
begin
part1
rescue Exception, SystemCallError, Errno::EACCES, Errno::EBUSY => e
part2
end

block and it is not catching a Errno::EBUSY error from part1: The
error crashes the program. So:
If that error has been raised in part1, and not caught in part1
then I should be able to catch it.
If that error has been raised in part1, and caught in part1,
part2 should never come into play, and the program should
continue

4 How can a subclass of Exception not be caught by Exception?
5 Why doesn't SystemCallError catch it?
6 Why doesn't the Errno::%s form catch it?

i.e everything between "Exception" and " =>" should be unnecessary,
but even with those it doesn't catch the error.

This sounds really strange. Here's what I'd do: use set_trace_func to
write out all method invocations, returns and thread ids and try to see
where it goes. HTH

Kind regards

robert
 
H

Hugh Sasse

Well, there's at least the theoretical chance that this block just calls
#exit... :)

It died with a stack trace. exit doesn't do that.
This sounds really strange. Here's what I'd do: use set_trace_func to
write out all method invocations, returns and thread ids and try to see
where it goes. HTH

Good job I tried to add docs to profiler.rb in stdlib yesterday.
I'll have a go at that. I've not played with set_trace_func before,
so it should be interesting.
Kind regards

robert
Hugh
 
R

Robert Klemme

Hugh said:
It died with a stack trace. exit doesn't do that.

Yes, but it's not too difficult to print a stack trace like the one you
see when the interpreter writs it.
Good job I tried to add docs to profiler.rb in stdlib yesterday.
I'll have a go at that. I've not played with set_trace_func before,
so it should be interesting.

This should get you started:

set_trace_func lambda {|event, file, line, id, binding, classname|
$stderr.printf "th %10d: %-10s %s: %d\n", Thread.current.object_id,
event, file, line if
/call|return/ =~ event
}

Good luck!

robert
 
H

Hugh Sasse

This should get you started:

set_trace_func lambda {|event, file, line, id, binding, classname|
$stderr.printf "th %10d: %-10s %s: %d\n", Thread.current.object_id,
event, file, line if
/call|return/ =~ event
}

Good luck!

thanks. It did the trick. I was hoisted by my own petard.

begin
copy_entry s, d, options[:preserve], options[:dereference_root]
rescue Exception, SystemCallError => e
stop = true
if block_given?
stop = yield s,d,e
end
raise if stop
end

Yes, I forgot to return nil from the *flaming* block! So it re-raised
because stop was true.

I didn't realize set_trace_func could be so useful. I'd previously
considered it obscure. Thank you for helping me out with this.

Hugh
 
R

Robert Klemme

Hugh said:
thanks. It did the trick. I was hoisted by my own petard.

Although the meaning is obvious I had to look up "petard". Did you know
your proverb originates from Shaky?
http://dictionary.reference.com/search?q=petard
begin
copy_entry s, d, options[:preserve],
options[:dereference_root] rescue Exception, SystemCallError =>
e stop = true
if block_given?
stop = yield s,d,e
end
raise if stop
end

Yes, I forgot to return nil from the *flaming* block! So it re-raised
because stop was true.

:)) Silly us - this happens to me once in a while, too.
I didn't realize set_trace_func could be so useful. I'd previously
considered it obscure. Thank you for helping me out with this.

You're welcome! I'm glad you finally managed to kill the beast. :)

Kind regards

robert
 
H

Hugh Sasse

Although the meaning is obvious I had to look up "petard". Did you know
your proverb originates from Shaky?
http://dictionary.reference.com/search?q=petard

I thought you meant "Shaking Stevens" for a minute! I know it was
Shakespearian in origin, but didn't know about the French and Latin
roots. Makes one wonder about "petition"...
begin
copy_entry s, d, options[:preserve],
options[:dereference_root] rescue Exception, SystemCallError =>
e stop = true
if block_given?
stop = yield s,d,e
end
raise if stop
end

Yes, I forgot to return nil from the *flaming* block! So it re-raised
because stop was true.

:)) Silly us - this happens to me once in a while, too.

It seems to take so long to figure out. Anyway, now I've a
shortcut.
You're welcome! I'm glad you finally managed to kill the beast. :)

Kind regards

robert
Thank you,
Hugh
 
C

Christophe Grandsire

Selon Hugh Sasse said:
I thought you meant "Shaking Stevens" for a minute! I know it was
Shakespearian in origin, but didn't know about the French and Latin
roots. Makes one wonder about "petition"...

It does come from Latin through French too, but luckily it doesn't have t=
he same
Latin root ;) . In this case, the root is the Latin verb petere: to ask, =
to
request. Yeah, I know it's disappointing ;) .

See: http://dictionary.reference.com/search?q=3Dpetition
--
Christophe Grandsire.

http://rainbow.conlang.free.fr

It takes a straight mind to create a twisted conlang.
 
H

Hugh Sasse

It does come from Latin through French too, but luckily it doesn't have the same

I should have put a smiley there :)
Latin root ;) . In this case, the root is the Latin verb petere: to ask, to

:) "simon, I shall call you Peter, because you will be asked a
question three times..." :) [Yes, I know it was Petrus]
request. Yeah, I know it's disappointing ;) .

See: http://dictionary.reference.com/search?q=petition
--
Christophe Grandsire.

http://rainbow.conlang.free.fr

It takes a straight mind to create a twisted conlang.

And I was thinking of the latin contribution to Esperanto, but then
peteri would be to ask part of a question :) Well, possibly only
to the extent that litero should be part of a bed!

Hugh
 
C

Christophe Grandsire

Selon Hugh Sasse said:
I should have put a smiley there :)

I did understand it without the smiley, which is why I added the "Yeah, I=
know
it's disappointing ;)" sentence. OK, my humour sucks sometimes... OK, oft=
en...
;)
And I was thinking of the latin contribution to Esperanto, but then
peteri would be to ask part of a question :) Well, possibly only
to the extent that litero should be part of a bed!

LOL!
--
Christophe Grandsire.

http://rainbow.conlang.free.fr

It takes a straight mind to create a twisted conlang.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,011
Latest member
AjaUqq1950

Latest Threads

Top