Bug #3556
closedFileUtils.mkdir_p fails trying to create C: under Windows
Description
=begin
Hello,
I've been experiencing weird problems with FileUtils.mkdir_p in RubyGems, as tried to document in [ruby-talk:365540]
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/365540
The root of this investigation is a report about gem installation under Windows:
http://groups.google.com/group/rubyinstaller/browse_thread/thread/df7b7c217ad7d882
We have been trying to reproduce this without avail.
Now, I was able to recreate a scenario:
ruby -v -rfileutils -e "system('rd C:\Foo /s/q'); puts FileUtils.mkdir_p('C:/Foo/Bar')"
C:/Users/Luis/Tools/Ruby/ruby-1.8.6-p398-i386-mingw32/lib/ruby/1.8/fileutils.rb:243:in mkdir': File exists - C: (Errno::EEXIST) from C:/Users/Luis/Tools/Ruby/ruby-1.8.6-p398-i386-mingw32/lib/ruby/1.8/fileutils.rb:243:in
fu_mkdir'
from C:/Users/Luis/Tools/Ruby/ruby-1.8.6-p398-i386-mingw32/lib/ruby/1.8/fileutils.rb:217:in mkdir_p' from C:/Users/Luis/Tools/Ruby/ruby-1.8.6-p398-i386-mingw32/lib/ruby/1.8/fileutils.rb:215:in
reverse_each'
from C:/Users/Luis/Tools/Ruby/ruby-1.8.6-p398-i386-mingw32/lib/ruby/1.8/fileutils.rb:215:in mkdir_p' from C:/Users/Luis/Tools/Ruby/ruby-1.8.6-p398-i386-mingw32/lib/ruby/1.8/fileutils.rb:201:in
each'
from C:/Users/Luis/Tools/Ruby/ruby-1.8.6-p398-i386-mingw32/lib/ruby/1.8/fileutils.rb:201:in `mkdir_p'
from -e:1
C:/Users/Luis/Tools/Ruby/ruby-1.9.1-p378-i386-mingw32/lib/ruby/1.9.1/fileutils.rb:243:in mkdir': File exists - C: (Errno::EEXIST) from C:/Users/Luis/Tools/Ruby/ruby-1.9.1-p378-i386-mingw32/lib/ruby/1.9.1/fileutils.rb:243:in
fu_mkdir'
from C:/Users/Luis/Tools/Ruby/ruby-1.9.1-p378-i386-mingw32/lib/ruby/1.9.1/fileutils.rb:217:in block (2 levels) in mkdir_p' from C:/Users/Luis/Tools/Ruby/ruby-1.9.1-p378-i386-mingw32/lib/ruby/1.9.1/fileutils.rb:215:in
reverse_each'
from C:/Users/Luis/Tools/Ruby/ruby-1.9.1-p378-i386-mingw32/lib/ruby/1.9.1/fileutils.rb:215:in block in mkdir_p' from C:/Users/Luis/Tools/Ruby/ruby-1.9.1-p378-i386-mingw32/lib/ruby/1.9.1/fileutils.rb:201:in
each'
from C:/Users/Luis/Tools/Ruby/ruby-1.9.1-p378-i386-mingw32/lib/ruby/1.9.1/fileutils.rb:201:in mkdir_p' from -e:1:in
'
C:/Users/Luis/Tools/Ruby/ruby-1.9.2-rc1-i386-mingw32/lib/ruby/1.9.1/fileutils.rb:243:in mkdir': File exists - C: (Errno::EEXIST) from C:/Users/Luis/Tools/Ruby/ruby-1.9.2-rc1-i386-mingw32/lib/ruby/1.9.1/fileutils.rb:243:in
fu_mkdir'
from C:/Users/Luis/Tools/Ruby/ruby-1.9.2-rc1-i386-mingw32/lib/ruby/1.9.1/fileutils.rb:217:in block (2 levels) in mkdir_p' from C:/Users/Luis/Tools/Ruby/ruby-1.9.2-rc1-i386-mingw32/lib/ruby/1.9.1/fileutils.rb:215:in
reverse_each'
from C:/Users/Luis/Tools/Ruby/ruby-1.9.2-rc1-i386-mingw32/lib/ruby/1.9.1/fileutils.rb:215:in block in mkdir_p' from C:/Users/Luis/Tools/Ruby/ruby-1.9.2-rc1-i386-mingw32/lib/ruby/1.9.1/fileutils.rb:201:in
each'
from C:/Users/Luis/Tools/Ruby/ruby-1.9.2-rc1-i386-mingw32/lib/ruby/1.9.1/fileutils.rb:201:in mkdir_p' from -e:1:in
'
And same happens with trunk.
After digging a little bit on test_fileutils.rb found that even running the tests fails:
- Error:
test_mkdir_p(TestFileUtils):
Errno::EEXIST: File exists - C:
test/fileutils/test_fileutils.rb:767:inblock in test_mkdir_p' test/fileutils/test_fileutils.rb:766:in
each'
test/fileutils/test_fileutils.rb:766:in `test_mkdir_p'
53 tests, 278 assertions, 0 failures, 1 errors, 0 skips
Further investigation pointed that the rescue of SystemCallError in mkdir_p is evaluating for File.directory? of dir, when dir has actually been altered inside fu_mkdir:
path = path.sub(%r</\z>, '')
But this change, been made in a local variable, is not seen by mkdir_p.
So:
- mkdir_p sends "C:/" to fu_mkdir
- fu_mkdir trims it to "C:"
- fu_mkdir invokes Dir.mkdir "C:" and Errno::EEXIST is raised
- mkdir_p catches it but evaluates File.directory? for "C:/" instead of "C:"
- mkdir_p re-raises the exception since File.directory?("C:/") == false
If I'm not missing something, that is wrong.
Attached is a simple patch that correct the issue, however, I think the real problem is File.stat that fails:
C:\Users\Luis>ruby -v -e "puts File.stat('C:').inspect; puts File.stat('C:/').inspect"
ruby 1.9.2dev (2010-07-02) [i386-mingw32]
#<File::Stat dev=0x2, ino=0, mode=040755, nlink=1, uid=0, gid=0, rdev=0x2, size=0, blksize=nil, blocks=nil, atime=2010-07-10 17:12:25 -0300, mtime=2010-07-10 17:12:25 -0300, ctime=2010-02-28 11:29:05 -0300>
-e:1:in stat': Permission denied - C:/ (Errno::EACCES) from -e:1:in
'
You receive Errno::EACCES for C:/, but that doesn't happen on JRuby or IronRuby:
IronRuby 1.0.0.0 on .NET 4.0.30319.1
#<File::Stat dev=3, ino=0, mode=16768, nlink=1, uid=0, gid=0, rdev=3, size=0, blksize=nil, blocks=nil, atime=Sat Jul 10
17:12:25 -0300 2010, mtime=Sat Jul 10 17:12:25 -0300 2010, ctime=Sun Feb 28 11:29:05 -0300 2010
#<File::Stat dev=3, ino=0, mode=16768, nlink=1, uid=0, gid=0, rdev=3, size=0, blksize=nil, blocks=nil, atime=Sat Jul 10
16:48:38 -0300 2010, mtime=Sat Jul 10 16:48:38 -0300 2010, ctime=Mon Jul 13 23:38:56 -0300 2009
jruby 1.4.1 (ruby 1.8.7 patchlevel 174) (2010-04-26 ea6db6a) (Java HotSpot(TM) Client VM 1.6.0_18) [x86-java]
#<File::Stat dev=0x2, ino=0, mode=040777, nlink=1, uid=0, gid=0, rdev=0x2, size=24576, blksize=0, blocks=0, atime=Tue Jan 01 00:00:00 -0300 1980, mtime=Tue Jan 01 00:00:00 -0300 1980, ctime=Tue Jan 01 00:00:00 -0300 1980>
#<File::Stat dev=0x2, ino=0, mode=040777, nlink=1, uid=0, gid=0, rdev=0x2, size=24576, blksize=0, blocks=0, atime=Tue Jan 01 00:00:00 -0300 1980, mtime=Tue Jan 01 00:00:00 -0300 1980, ctime=Tue Jan 01 00:00:00 -0300 1980>
jruby 1.5.1 (ruby 1.8.7 patchlevel 249) (2010-06-06 f3a3480) (Java HotSpot(TM) Client VM 1.6.0_18) [x86-java]
#<File::Stat dev=0x2, ino=0, mode=040755, nlink=1, uid=0, gid=0, rdev=0x2, size=24576, blksize=512, blocks=0, atime=Tue
Jan 01 00:00:00 -0300 1980, mtime=Tue Jan 01 00:00:00 -0300 1980, ctime=Tue Jan 01 00:00:00 -0300 1980>
#<File::Stat dev=0x2, ino=0, mode=040755, nlink=1, uid=0, gid=0, rdev=0x2, size=24576, blksize=512, blocks=0, atime=Tue
Jan 01 00:00:00 -0300 1980, mtime=Tue Jan 01 00:00:00 -0300 1980, ctime=Tue Jan 01 00:00:00 -0300 1980>
=end
Files