mirror of
https://github.com/python/cpython.git
synced 2025-08-04 00:48:58 +00:00
bpo-37834: Normalise handling of reparse points on Windows (GH-15231)
bpo-37834: Normalise handling of reparse points on Windows * ntpath.realpath() and nt.stat() will traverse all supported reparse points (previously was mixed) * nt.lstat() will let the OS traverse reparse points that are not name surrogates (previously would not traverse any reparse point) * nt.[l]stat() will only set S_IFLNK for symlinks (previous behaviour) * nt.readlink() will read destinations for symlinks and junction points only bpo-1311: os.path.exists('nul') now returns True on Windows * nt.stat('nul').st_mode is now S_IFCHR (previously was an error)
This commit is contained in:
parent
bcc446f525
commit
df2d4a6f3d
16 changed files with 477 additions and 240 deletions
|
@ -42,6 +42,11 @@ try:
|
|||
except ImportError:
|
||||
UID_GID_SUPPORT = False
|
||||
|
||||
try:
|
||||
import _winapi
|
||||
except ImportError:
|
||||
_winapi = None
|
||||
|
||||
def _fake_rename(*args, **kwargs):
|
||||
# Pretend the destination path is on a different filesystem.
|
||||
raise OSError(getattr(errno, 'EXDEV', 18), "Invalid cross-device link")
|
||||
|
@ -226,6 +231,47 @@ class TestShutil(unittest.TestCase):
|
|||
self.assertTrue(os.path.exists(dir3))
|
||||
self.assertTrue(os.path.exists(file1))
|
||||
|
||||
@unittest.skipUnless(_winapi, 'only relevant on Windows')
|
||||
def test_rmtree_fails_on_junctions(self):
|
||||
tmp = self.mkdtemp()
|
||||
dir_ = os.path.join(tmp, 'dir')
|
||||
os.mkdir(dir_)
|
||||
link = os.path.join(tmp, 'link')
|
||||
_winapi.CreateJunction(dir_, link)
|
||||
self.assertRaises(OSError, shutil.rmtree, link)
|
||||
self.assertTrue(os.path.exists(dir_))
|
||||
self.assertTrue(os.path.lexists(link))
|
||||
errors = []
|
||||
def onerror(*args):
|
||||
errors.append(args)
|
||||
shutil.rmtree(link, onerror=onerror)
|
||||
self.assertEqual(len(errors), 1)
|
||||
self.assertIs(errors[0][0], os.path.islink)
|
||||
self.assertEqual(errors[0][1], link)
|
||||
self.assertIsInstance(errors[0][2][1], OSError)
|
||||
|
||||
@unittest.skipUnless(_winapi, 'only relevant on Windows')
|
||||
def test_rmtree_works_on_junctions(self):
|
||||
tmp = self.mkdtemp()
|
||||
dir1 = os.path.join(tmp, 'dir1')
|
||||
dir2 = os.path.join(dir1, 'dir2')
|
||||
dir3 = os.path.join(tmp, 'dir3')
|
||||
for d in dir1, dir2, dir3:
|
||||
os.mkdir(d)
|
||||
file1 = os.path.join(tmp, 'file1')
|
||||
write_file(file1, 'foo')
|
||||
link1 = os.path.join(dir1, 'link1')
|
||||
_winapi.CreateJunction(dir2, link1)
|
||||
link2 = os.path.join(dir1, 'link2')
|
||||
_winapi.CreateJunction(dir3, link2)
|
||||
link3 = os.path.join(dir1, 'link3')
|
||||
_winapi.CreateJunction(file1, link3)
|
||||
# make sure junctions are removed but not followed
|
||||
shutil.rmtree(dir1)
|
||||
self.assertFalse(os.path.exists(dir1))
|
||||
self.assertTrue(os.path.exists(dir3))
|
||||
self.assertTrue(os.path.exists(file1))
|
||||
|
||||
def test_rmtree_errors(self):
|
||||
# filename is guaranteed not to exist
|
||||
filename = tempfile.mktemp()
|
||||
|
@ -754,8 +800,12 @@ class TestShutil(unittest.TestCase):
|
|||
src_stat = os.lstat(src_link)
|
||||
shutil.copytree(src_dir, dst_dir, symlinks=True)
|
||||
self.assertTrue(os.path.islink(os.path.join(dst_dir, 'sub', 'link')))
|
||||
self.assertEqual(os.readlink(os.path.join(dst_dir, 'sub', 'link')),
|
||||
os.path.join(src_dir, 'file.txt'))
|
||||
actual = os.readlink(os.path.join(dst_dir, 'sub', 'link'))
|
||||
# Bad practice to blindly strip the prefix as it may be required to
|
||||
# correctly refer to the file, but we're only comparing paths here.
|
||||
if os.name == 'nt' and actual.startswith('\\\\?\\'):
|
||||
actual = actual[4:]
|
||||
self.assertEqual(actual, os.path.join(src_dir, 'file.txt'))
|
||||
dst_stat = os.lstat(dst_link)
|
||||
if hasattr(os, 'lchmod'):
|
||||
self.assertEqual(dst_stat.st_mode, src_stat.st_mode)
|
||||
|
@ -886,7 +936,6 @@ class TestShutil(unittest.TestCase):
|
|||
shutil.copytree(src, dst, copy_function=custom_cpfun)
|
||||
self.assertEqual(len(flag), 1)
|
||||
|
||||
@unittest.skipIf(os.name == 'nt', 'temporarily disabled on Windows')
|
||||
@unittest.skipUnless(hasattr(os, 'link'), 'requires os.link')
|
||||
def test_dont_copy_file_onto_link_to_itself(self):
|
||||
# bug 851123.
|
||||
|
@ -941,6 +990,20 @@ class TestShutil(unittest.TestCase):
|
|||
finally:
|
||||
shutil.rmtree(TESTFN, ignore_errors=True)
|
||||
|
||||
@unittest.skipUnless(_winapi, 'only relevant on Windows')
|
||||
def test_rmtree_on_junction(self):
|
||||
os.mkdir(TESTFN)
|
||||
try:
|
||||
src = os.path.join(TESTFN, 'cheese')
|
||||
dst = os.path.join(TESTFN, 'shop')
|
||||
os.mkdir(src)
|
||||
open(os.path.join(src, 'spam'), 'wb').close()
|
||||
_winapi.CreateJunction(src, dst)
|
||||
self.assertRaises(OSError, shutil.rmtree, dst)
|
||||
shutil.rmtree(dst, ignore_errors=True)
|
||||
finally:
|
||||
shutil.rmtree(TESTFN, ignore_errors=True)
|
||||
|
||||
# Issue #3002: copyfile and copytree block indefinitely on named pipes
|
||||
@unittest.skipUnless(hasattr(os, "mkfifo"), 'requires os.mkfifo()')
|
||||
def test_copyfile_named_pipe(self):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue