summaryrefslogtreecommitdiffstats
path: root/Lib/distutils
diff options
context:
space:
mode:
authorAntoine Pitrou <solipsis@pitrou.net>2014-10-30 18:37:07 (GMT)
committerAntoine Pitrou <solipsis@pitrou.net>2014-10-30 18:37:07 (GMT)
commit78be2f4e01046e17a5c1a32fe6823a776b044ef7 (patch)
tree1c2f33898833001189ff1074cdc3eb5cee0b7691 /Lib/distutils
parent0f5d6c00b48fb5e10e19909f5a8ca48bd2b9b0f5 (diff)
downloadcpython-78be2f4e01046e17a5c1a32fe6823a776b044ef7.zip
cpython-78be2f4e01046e17a5c1a32fe6823a776b044ef7.tar.gz
cpython-78be2f4e01046e17a5c1a32fe6823a776b044ef7.tar.bz2
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.
Diffstat (limited to 'Lib/distutils')
-rw-r--r--Lib/distutils/file_util.py34
-rw-r--r--Lib/distutils/tests/test_file_util.py43
2 files changed, 64 insertions, 13 deletions
diff --git a/Lib/distutils/file_util.py b/Lib/distutils/file_util.py
index b9f0786..3b236e1 100644
--- a/Lib/distutils/file_util.py
+++ b/Lib/distutils/file_util.py
@@ -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
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
- 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
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,
# (Unix only, of course, but that's the caller's responsibility)
if link == 'hard':
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':
if not (os.path.exists(dst) and os.path.samefile(src, dst)):
os.symlink(src, dst)
+ return (dst, 1)
# Otherwise (non-Mac, not linking), copy the file contents and
# (optionally) copy the times and mode.
- else:
- _copy_file_contents(src, dst)
- if preserve_mode or preserve_times:
- st = os.stat(src)
-
- # According to David Ascher <da@ski.org>, utime() should be done
- # before chmod() (at least under NT).
- if preserve_times:
- os.utime(dst, (st[ST_ATIME], st[ST_MTIME]))
- if preserve_mode:
- os.chmod(dst, S_IMODE(st[ST_MODE]))
+ _copy_file_contents(src, dst)
+ if preserve_mode or preserve_times:
+ st = os.stat(src)
+
+ # According to David Ascher <da@ski.org>, utime() should be done
+ # before chmod() (at least under NT).
+ if preserve_times:
+ os.utime(dst, (st[ST_ATIME], st[ST_MTIME]))
+ if preserve_mode:
+ os.chmod(dst, S_IMODE(st[ST_MODE]))
return (dst, 1)
diff --git a/Lib/distutils/tests/test_file_util.py b/Lib/distutils/tests/test_file_util.py
index 7dbcf52..686ba3c 100644
--- a/Lib/distutils/tests/test_file_util.py
+++ b/Lib/distutils/tests/test_file_util.py
@@ -8,6 +8,11 @@ from distutils import log
from distutils.tests import support
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):
def _log(self, msg, *args):
@@ -74,6 +79,44 @@ class FileUtilTestCase(support.TempdirManager, unittest.TestCase):
copy_file(foo, dst_dir)
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():
return unittest.makeSuite(FileUtilTestCase)