Commit 78be2f4e authored by Antoine Pitrou's avatar Antoine Pitrou

Issue #8876: distutils now falls back to copying files when hard linking doesn't work.

This allows use with special filesystems such as VirtualBox shared folders.
parent 0f5d6c00
...@@ -85,7 +85,8 @@ def copy_file(src, dst, preserve_mode=1, preserve_times=1, update=0, ...@@ -85,7 +85,8 @@ def copy_file(src, dst, preserve_mode=1, preserve_times=1, update=0,
(os.symlink) instead of copying: set it to "hard" or "sym"; if it is (os.symlink) instead of copying: set it to "hard" or "sym"; if it is
None (the default), files are copied. Don't set 'link' on systems that None (the default), files are copied. Don't set 'link' on systems that
don't support it: 'copy_file()' doesn't check if hard or symbolic don't support it: 'copy_file()' doesn't check if hard or symbolic
linking is available. linking is available. If hardlink fails, falls back to
_copy_file_contents().
Under Mac OS, uses the native file copy function in macostools; on Under Mac OS, uses the native file copy function in macostools; on
other systems, uses '_copy_file_contents()' to copy file contents. other systems, uses '_copy_file_contents()' to copy file contents.
...@@ -137,24 +138,31 @@ def copy_file(src, dst, preserve_mode=1, preserve_times=1, update=0, ...@@ -137,24 +138,31 @@ def copy_file(src, dst, preserve_mode=1, preserve_times=1, update=0,
# (Unix only, of course, but that's the caller's responsibility) # (Unix only, of course, but that's the caller's responsibility)
if link == 'hard': if link == 'hard':
if not (os.path.exists(dst) and os.path.samefile(src, dst)): if not (os.path.exists(dst) and os.path.samefile(src, dst)):
os.link(src, dst) try:
os.link(src, dst)
return (dst, 1)
except OSError:
# If hard linking fails, fall back on copying file
# (some special filesystems don't support hard linking
# even under Unix, see issue #8876).
pass
elif link == 'sym': elif link == 'sym':
if not (os.path.exists(dst) and os.path.samefile(src, dst)): if not (os.path.exists(dst) and os.path.samefile(src, dst)):
os.symlink(src, dst) os.symlink(src, dst)
return (dst, 1)
# Otherwise (non-Mac, not linking), copy the file contents and # Otherwise (non-Mac, not linking), copy the file contents and
# (optionally) copy the times and mode. # (optionally) copy the times and mode.
else: _copy_file_contents(src, dst)
_copy_file_contents(src, dst) if preserve_mode or preserve_times:
if preserve_mode or preserve_times: st = os.stat(src)
st = os.stat(src)
# According to David Ascher <da@ski.org>, utime() should be done
# According to David Ascher <da@ski.org>, utime() should be done # before chmod() (at least under NT).
# before chmod() (at least under NT). if preserve_times:
if preserve_times: os.utime(dst, (st[ST_ATIME], st[ST_MTIME]))
os.utime(dst, (st[ST_ATIME], st[ST_MTIME])) if preserve_mode:
if preserve_mode: os.chmod(dst, S_IMODE(st[ST_MODE]))
os.chmod(dst, S_IMODE(st[ST_MODE]))
return (dst, 1) return (dst, 1)
......
...@@ -8,6 +8,11 @@ from distutils import log ...@@ -8,6 +8,11 @@ from distutils import log
from distutils.tests import support from distutils.tests import support
from test.test_support import run_unittest from test.test_support import run_unittest
requires_os_link = unittest.skipUnless(hasattr(os, "link"),
"test requires os.link()")
class FileUtilTestCase(support.TempdirManager, unittest.TestCase): class FileUtilTestCase(support.TempdirManager, unittest.TestCase):
def _log(self, msg, *args): def _log(self, msg, *args):
...@@ -74,6 +79,44 @@ class FileUtilTestCase(support.TempdirManager, unittest.TestCase): ...@@ -74,6 +79,44 @@ class FileUtilTestCase(support.TempdirManager, unittest.TestCase):
copy_file(foo, dst_dir) copy_file(foo, dst_dir)
self.assertTrue(os.path.exists(os.path.join(dst_dir, 'foo'))) self.assertTrue(os.path.exists(os.path.join(dst_dir, 'foo')))
@requires_os_link
def test_copy_file_hard_link(self):
with open(self.source, 'w') as f:
f.write('some content')
st = os.stat(self.source)
copy_file(self.source, self.target, link='hard')
st2 = os.stat(self.source)
st3 = os.stat(self.target)
self.assertTrue(os.path.samestat(st, st2), (st, st2))
self.assertTrue(os.path.samestat(st2, st3), (st2, st3))
with open(self.source, 'r') as f:
self.assertEqual(f.read(), 'some content')
@requires_os_link
def test_copy_file_hard_link_failure(self):
# If hard linking fails, copy_file() falls back on copying file
# (some special filesystems don't support hard linking even under
# Unix, see issue #8876).
with open(self.source, 'w') as f:
f.write('some content')
st = os.stat(self.source)
def _os_link(*args):
raise OSError(0, "linking unsupported")
old_link = os.link
os.link = _os_link
try:
copy_file(self.source, self.target, link='hard')
finally:
os.link = old_link
st2 = os.stat(self.source)
st3 = os.stat(self.target)
self.assertTrue(os.path.samestat(st, st2), (st, st2))
self.assertFalse(os.path.samestat(st2, st3), (st2, st3))
for fn in (self.source, self.target):
with open(fn, 'r') as f:
self.assertEqual(f.read(), 'some content')
def test_suite(): def test_suite():
return unittest.makeSuite(FileUtilTestCase) return unittest.makeSuite(FileUtilTestCase)
......
...@@ -37,6 +37,10 @@ Core and Builtins ...@@ -37,6 +37,10 @@ Core and Builtins
Library Library
------- -------
- Issue #8876: distutils now falls back to copying files when hard linking
doesn't work. This allows use with special filesystems such as VirtualBox
shared folders.
- Issue #9351: Defaults set with set_defaults on an argparse subparser - Issue #9351: Defaults set with set_defaults on an argparse subparser
are no longer ignored when also set on the parent parser. are no longer ignored when also set on the parent parser.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment