Commit 82b595a2 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.
parents 43d2a0d4 909f8ed5
...@@ -80,7 +80,8 @@ def copy_file(src, dst, preserve_mode=1, preserve_times=1, update=0, ...@@ -80,7 +80,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.
...@@ -132,24 +133,31 @@ def copy_file(src, dst, preserve_mode=1, preserve_times=1, update=0, ...@@ -132,24 +133,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)
elif link == 'hard': elif 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)
......
...@@ -5,7 +5,7 @@ import shutil ...@@ -5,7 +5,7 @@ import shutil
import errno import errno
from unittest.mock import patch from unittest.mock import patch
from distutils.file_util import move_file from distutils.file_util import move_file, copy_file
from distutils import log from distutils import log
from distutils.tests import support from distutils.tests import support
from distutils.errors import DistutilsFileError from distutils.errors import DistutilsFileError
...@@ -78,6 +78,36 @@ class FileUtilTestCase(support.TempdirManager, unittest.TestCase): ...@@ -78,6 +78,36 @@ class FileUtilTestCase(support.TempdirManager, unittest.TestCase):
fobj.write('spam eggs') fobj.write('spam eggs')
move_file(self.source, self.target, verbose=0) move_file(self.source, self.target, verbose=0)
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')
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)
with patch("os.link", side_effect=OSError(0, "linking unsupported")):
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.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)
......
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