[Python-checkins] cpython (merge 3.4 -> default): Issue #8876: distutils now falls back to copying files when hard linking
antoine.pitrou
python-checkins at python.org
Thu Oct 30 19:39:14 CET 2014
https://hg.python.org/cpython/rev/ce484e0840e3
changeset: 93263:ce484e0840e3
parent: 93261:f4f5b942e5e0
parent: 93262:d94d8789e924
user: Antoine Pitrou <solipsis at pitrou.net>
date: Thu Oct 30 19:38:33 2014 +0100
summary:
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.
files:
Lib/distutils/file_util.py | 32 +++++++++-----
Lib/distutils/tests/test_file_util.py | 32 ++++++++++++++-
Misc/NEWS | 5 +-
3 files changed, 55 insertions(+), 14 deletions(-)
diff --git a/Lib/distutils/file_util.py b/Lib/distutils/file_util.py
--- a/Lib/distutils/file_util.py
+++ b/Lib/distutils/file_util.py
@@ -80,7 +80,8 @@
(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.
@@ -132,24 +133,31 @@
# (Unix only, of course, but that's the caller's responsibility)
elif 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)
+ _copy_file_contents(src, dst)
+ if preserve_mode or preserve_times:
+ st = os.stat(src)
- # According to David Ascher <da at 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]))
+ # According to David Ascher <da at 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
--- a/Lib/distutils/tests/test_file_util.py
+++ b/Lib/distutils/tests/test_file_util.py
@@ -5,7 +5,7 @@
import errno
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.tests import support
from distutils.errors import DistutilsFileError
@@ -78,6 +78,36 @@
fobj.write('spam eggs')
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():
return unittest.makeSuite(FileUtilTestCase)
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -62,7 +62,6 @@
argument contains not permitted null character or byte.
- Issue #22258: Fix the internal function set_inheritable() on Illumos.
-
This platform exposes the function ``ioctl(FIOCLEX)``, but calling it fails
with errno is ENOTTY: "Inappropriate ioctl for device". set_inheritable()
now falls back to the slower ``fcntl()`` (``F_GETFD`` and then ``F_SETFD``).
@@ -181,6 +180,10 @@
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 #22217: Implemented reprs of classes in the zipfile module.
- Issue #18216: gettext now raises an error when a .mo file has an
--
Repository URL: https://hg.python.org/cpython
More information about the Python-checkins
mailing list