mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 21:51:50 +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 @@ | |||
| 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 @@ def test_rmtree_works_on_symlinks(self): | |||
|         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 @@ def test_copytree_symlinks(self): | |||
|         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 @@ def custom_cpfun(a, b): | |||
|         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 @@ def test_rmtree_on_symlink(self): | |||
|         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
	
	 Steve Dower
						Steve Dower