Bug #10104

Fileutils cp_r fails on sockets or fifes even if File.mknod and File.mkfifo are defined

Added by Bill Paulson over 2 years ago.

Target version:
ruby -v:
ruby 2.1.1p76 (2014-02-24 revision 45161) [x86_64-darwin12.0]


Briefly, our code uses cp_r to copy a directory structure whose contents seem to occasionally have a Unix domain socket. This raises an exception, which seems reasonable enough. We consulted the code to see if there was a workaround, and found that if the methods File.mknod and File.mkfifo where defined, it would avoid the exception, apparently by design. The problem being reported is that when File.mknod and File.mkfifo are defined, the copy method fails because it tries to call FileUtils::Entry_.mknod or FileUtils::Entry_.mkfifo, which do not exist. It appears that the "unless File.respond_to?(:mknod)" clause is irrelevant to the intended behavior.
In fileutils.rb, the copy method has checks that raises an exception when it tries to copy a Unix domain socket, fifo, or other special file. It will not raise if the File class responds to :mknod or :mkfifo.
In class Entry_, method copy() -
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

Some previous suggested that that if desired that creating File.mkfifo and File.mknod would allow the copy to continue. If the code called File.mkfifo this would be true, but since it calls FileUtils::Entry_.mkfifo, working around this exception needs more than just creating File methods.

The following (ugly, monkey patching) workaround allows copy to work correctly, while skipping special files:
File.singleton_class.module_eval("def mknod; end")
File.singleton_class.module_eval("def mkfifo; end")
FileUtils::Entry_.module_eval("def mknod(args); end")
FileUtils::Entry_.module_eval("def mkfifo(
args); end")

FileUtils.cp_r(old, new)

 File.singleton_class.module_eval("undef_method :mknod")
 File.singleton_class.module_eval("undef_method :mkfifo")

It appears that the intent of the existing code is to allow Unix/Linux programs to work (if necessary by creating File.mknod and File.mkfifo routines), while acknowledging that mkfifo and mknod aren't ubiquitous. In that case, the lines following each exception should call File.mknod or File.mkfifo, rather than*.

Also available in: Atom PDF