Bug #3556: FileUtils.mkdir_p fails trying to create C: under Windows
http://redmine.ruby-lang.org/issues/show/3556

Author: Luis Lavena
Status: Open, Priority: Normal
Target version: 1.9.x
ruby -v: 1.8 and 1.9, including trunk

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 `<main>'

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 `<main>'

And same happens with trunk.

After digging a little bit on test_fileutils.rb found that even running the tests fails:

  1) Error:
test_mkdir_p(TestFileUtils):
Errno::EEXIST: File exists - C:
    test/fileutils/test_fileutils.rb:767:in `block 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:

1) mkdir_p sends "C:/" to fu_mkdir
2) fu_mkdir trims it to "C:"
3) fu_mkdir invokes Dir.mkdir "C:" and Errno::EEXIST is raised
4) mkdir_p catches it but evaluates File.directory? for "C:/" instead of "C:"
5) 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 `<main>'

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>


----------------------------------------
http://redmine.ruby-lang.org