Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ed14c86

Browse files
committed
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.
1 parent 5e78f4d commit ed14c86

3 files changed

Lines changed: 56 additions & 14 deletions

File tree

Lib/distutils/file_util.py

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ def copy_file(src, dst, preserve_mode=1, preserve_times=1, update=0,
8080
(os.symlink) instead of copying: set it to "hard" or "sym"; if it is
8181
None (the default), files are copied. Don't set 'link' on systems that
8282
don't support it: 'copy_file()' doesn't check if hard or symbolic
83-
linking is available.
83+
linking is available. If hardlink fails, falls back to
84+
_copy_file_contents().
8485
8586
Under Mac OS, uses the native file copy function in macostools; on
8687
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,
132133
# (Unix only, of course, but that's the caller's responsibility)
133134
elif link == 'hard':
134135
if not (os.path.exists(dst) and os.path.samefile(src, dst)):
135-
os.link(src, dst)
136+
try:
137+
os.link(src, dst)
138+
return (dst, 1)
139+
except OSError:
140+
# If hard linking fails, fall back on copying file
141+
# (some special filesystems don't support hard linking
142+
# even under Unix, see issue #8876).
143+
pass
136144
elif link == 'sym':
137145
if not (os.path.exists(dst) and os.path.samefile(src, dst)):
138146
os.symlink(src, dst)
147+
return (dst, 1)
139148

140149
# Otherwise (non-Mac, not linking), copy the file contents and
141150
# (optionally) copy the times and mode.
142-
else:
143-
_copy_file_contents(src, dst)
144-
if preserve_mode or preserve_times:
145-
st = os.stat(src)
146-
147-
# According to David Ascher <[email protected]>, utime() should be done
148-
# before chmod() (at least under NT).
149-
if preserve_times:
150-
os.utime(dst, (st[ST_ATIME], st[ST_MTIME]))
151-
if preserve_mode:
152-
os.chmod(dst, S_IMODE(st[ST_MODE]))
151+
_copy_file_contents(src, dst)
152+
if preserve_mode or preserve_times:
153+
st = os.stat(src)
154+
155+
# According to David Ascher <[email protected]>, utime() should be done
156+
# before chmod() (at least under NT).
157+
if preserve_times:
158+
os.utime(dst, (st[ST_ATIME], st[ST_MTIME]))
159+
if preserve_mode:
160+
os.chmod(dst, S_IMODE(st[ST_MODE]))
153161

154162
return (dst, 1)
155163

Lib/distutils/tests/test_file_util.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import errno
66
from unittest.mock import patch
77

8-
from distutils.file_util import move_file
8+
from distutils.file_util import move_file, copy_file
99
from distutils import log
1010
from distutils.tests import support
1111
from distutils.errors import DistutilsFileError
@@ -78,6 +78,36 @@ def test_move_file_exception_unpacking_unlink(self):
7878
fobj.write('spam eggs')
7979
move_file(self.source, self.target, verbose=0)
8080

81+
def test_copy_file_hard_link(self):
82+
with open(self.source, 'w') as f:
83+
f.write('some content')
84+
st = os.stat(self.source)
85+
copy_file(self.source, self.target, link='hard')
86+
st2 = os.stat(self.source)
87+
st3 = os.stat(self.target)
88+
self.assertTrue(os.path.samestat(st, st2), (st, st2))
89+
self.assertTrue(os.path.samestat(st2, st3), (st2, st3))
90+
with open(self.source, 'r') as f:
91+
self.assertEqual(f.read(), 'some content')
92+
93+
def test_copy_file_hard_link_failure(self):
94+
# If hard linking fails, copy_file() falls back on copying file
95+
# (some special filesystems don't support hard linking even under
96+
# Unix, see issue #8876).
97+
with open(self.source, 'w') as f:
98+
f.write('some content')
99+
st = os.stat(self.source)
100+
with patch("os.link", side_effect=OSError(0, "linking unsupported")):
101+
copy_file(self.source, self.target, link='hard')
102+
st2 = os.stat(self.source)
103+
st3 = os.stat(self.target)
104+
self.assertTrue(os.path.samestat(st, st2), (st, st2))
105+
self.assertFalse(os.path.samestat(st2, st3), (st2, st3))
106+
for fn in (self.source, self.target):
107+
with open(fn, 'r') as f:
108+
self.assertEqual(f.read(), 'some content')
109+
110+
81111
def test_suite():
82112
return unittest.makeSuite(FileUtilTestCase)
83113

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ Core and Builtins
3333
Library
3434
-------
3535

36+
- Issue #8876: distutils now falls back to copying files when hard linking
37+
doesn't work. This allows use with special filesystems such as VirtualBox
38+
shared folders.
39+
3640
- Issue #18853: Fixed ResourceWarning in shlex.__nain__.
3741

3842
- Issue #9351: Defaults set with set_defaults on an argparse subparser

0 commit comments

Comments
 (0)