mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 19:34:08 +00:00 
			
		
		
		
	bpo-46785: Fix race condition between os.stat() and unlink on Windows (GH-31858)
This commit is contained in:
		
							parent
							
								
									ebb8b512e9
								
							
						
					
					
						commit
						39e6b8ae6a
					
				
					 4 changed files with 57 additions and 1 deletions
				
			
		| 
						 | 
					@ -24,6 +24,7 @@ import subprocess
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
import sysconfig
 | 
					import sysconfig
 | 
				
			||||||
import tempfile
 | 
					import tempfile
 | 
				
			||||||
 | 
					import textwrap
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
import types
 | 
					import types
 | 
				
			||||||
import unittest
 | 
					import unittest
 | 
				
			||||||
| 
						 | 
					@ -2883,6 +2884,49 @@ class Win32NtTests(unittest.TestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertEqual(0, handle_delta)
 | 
					        self.assertEqual(0, handle_delta)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @support.requires_subprocess()
 | 
				
			||||||
 | 
					    def test_stat_unlink_race(self):
 | 
				
			||||||
 | 
					        # bpo-46785: the implementation of os.stat() falls back to reading
 | 
				
			||||||
 | 
					        # the parent directory if CreateFileW() fails with a permission
 | 
				
			||||||
 | 
					        # error. If reading the parent directory fails because the file or
 | 
				
			||||||
 | 
					        # directory are subsequently unlinked, or because the volume or
 | 
				
			||||||
 | 
					        # share are no longer available, then the original permission error
 | 
				
			||||||
 | 
					        # should not be restored.
 | 
				
			||||||
 | 
					        filename =  os_helper.TESTFN
 | 
				
			||||||
 | 
					        self.addCleanup(os_helper.unlink, filename)
 | 
				
			||||||
 | 
					        deadline = time.time() + 5
 | 
				
			||||||
 | 
					        command = textwrap.dedent("""\
 | 
				
			||||||
 | 
					            import os
 | 
				
			||||||
 | 
					            import sys
 | 
				
			||||||
 | 
					            import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            filename = sys.argv[1]
 | 
				
			||||||
 | 
					            deadline = float(sys.argv[2])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            while time.time() < deadline:
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    with open(filename, "w") as f:
 | 
				
			||||||
 | 
					                        pass
 | 
				
			||||||
 | 
					                except OSError:
 | 
				
			||||||
 | 
					                    pass
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    os.remove(filename)
 | 
				
			||||||
 | 
					                except OSError:
 | 
				
			||||||
 | 
					                    pass
 | 
				
			||||||
 | 
					            """)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with subprocess.Popen([sys.executable, '-c', command, filename, str(deadline)]) as proc:
 | 
				
			||||||
 | 
					            while time.time() < deadline:
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    os.stat(filename)
 | 
				
			||||||
 | 
					                except FileNotFoundError as e:
 | 
				
			||||||
 | 
					                    assert e.winerror == 2  # ERROR_FILE_NOT_FOUND
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                proc.wait(1)
 | 
				
			||||||
 | 
					            except subprocess.TimeoutExpired:
 | 
				
			||||||
 | 
					                proc.terminate()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@os_helper.skip_unless_symlink
 | 
					@os_helper.skip_unless_symlink
 | 
				
			||||||
class NonLocalSymlinkTests(unittest.TestCase):
 | 
					class NonLocalSymlinkTests(unittest.TestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1705,6 +1705,7 @@ Anthony Starks
 | 
				
			||||||
David Steele
 | 
					David Steele
 | 
				
			||||||
Oliver Steele
 | 
					Oliver Steele
 | 
				
			||||||
Greg Stein
 | 
					Greg Stein
 | 
				
			||||||
 | 
					Itai Steinherz
 | 
				
			||||||
Marek Stepniowski
 | 
					Marek Stepniowski
 | 
				
			||||||
Baruch Sterin
 | 
					Baruch Sterin
 | 
				
			||||||
Chris Stern
 | 
					Chris Stern
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					Fix race condition between :func:`os.stat` and unlinking a file on Windows, by using errors codes returned by ``FindFirstFileW()`` when appropriate in ``win32_xstat_impl``.
 | 
				
			||||||
| 
						 | 
					@ -1890,7 +1890,17 @@ win32_xstat_impl(const wchar_t *path, struct _Py_stat_struct *result,
 | 
				
			||||||
            /* Try reading the parent directory. */
 | 
					            /* Try reading the parent directory. */
 | 
				
			||||||
            if (!attributes_from_dir(path, &fileInfo, &tagInfo.ReparseTag)) {
 | 
					            if (!attributes_from_dir(path, &fileInfo, &tagInfo.ReparseTag)) {
 | 
				
			||||||
                /* Cannot read the parent directory. */
 | 
					                /* Cannot read the parent directory. */
 | 
				
			||||||
 | 
					                switch (GetLastError()) {
 | 
				
			||||||
 | 
					                case ERROR_FILE_NOT_FOUND: /* File cannot be found */
 | 
				
			||||||
 | 
					                case ERROR_PATH_NOT_FOUND: /* File parent directory cannot be found */
 | 
				
			||||||
 | 
					                case ERROR_NOT_READY: /* Drive exists but unavailable */
 | 
				
			||||||
 | 
					                case ERROR_BAD_NET_NAME: /* Remote drive unavailable */
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                /* Restore the error from CreateFileW(). */
 | 
				
			||||||
 | 
					                default:
 | 
				
			||||||
                    SetLastError(error);
 | 
					                    SetLastError(error);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return -1;
 | 
					                return -1;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
 | 
					            if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue