]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-111877: Fixes stat() handling for inaccessible files on Windows (GH-113716)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Fri, 12 Jan 2024 15:53:27 +0000 (16:53 +0100)
committerGitHub <noreply@github.com>
Fri, 12 Jan 2024 15:53:27 +0000 (15:53 +0000)
(cherry picked from commit ed066481c76c6888ff5709f5b9f93b92c232a4a6)

Co-authored-by: Steve Dower <steve.dower@python.org>
Lib/test/test_os.py
Misc/NEWS.d/next/Windows/2024-01-04-21-16-31.gh-issue-111877.fR-B4c.rst [new file with mode: 0644]
Modules/posixmodule.c

index 2d94951bf27f083678b60f357011d36c0f4d2dd5..fc58f8d2dcd7a343acbcc2acb21d5ed33269e8ba 100644 (file)
@@ -3077,6 +3077,66 @@ class Win32NtTests(unittest.TestCase):
             except subprocess.TimeoutExpired:
                 proc.terminate()
 
+    @support.requires_subprocess()
+    def test_stat_inaccessible_file(self):
+        filename = os_helper.TESTFN
+        ICACLS = os.path.expandvars(r"%SystemRoot%\System32\icacls.exe")
+
+        with open(filename, "wb") as f:
+            f.write(b'Test data')
+
+        stat1 = os.stat(filename)
+
+        try:
+            # Remove all permissions from the file
+            subprocess.check_output([ICACLS, filename, "/inheritance:r"],
+                                    stderr=subprocess.STDOUT)
+        except subprocess.CalledProcessError as ex:
+            if support.verbose:
+                print(ICACLS, filename, "/inheritance:r", "failed.")
+                print(ex.stdout.decode("oem", "replace").rstrip())
+            try:
+                os.unlink(filename)
+            except OSError:
+                pass
+            self.skipTest("Unable to create inaccessible file")
+
+        def cleanup():
+            # Give delete permission. We are the file owner, so we can do this
+            # even though we removed all permissions earlier.
+            subprocess.check_output([ICACLS, filename, "/grant", "Everyone:(D)"],
+                                    stderr=subprocess.STDOUT)
+            os.unlink(filename)
+
+        self.addCleanup(cleanup)
+
+        if support.verbose:
+            print("File:", filename)
+            print("stat with access:", stat1)
+
+        # First test - we shouldn't raise here, because we still have access to
+        # the directory and can extract enough information from its metadata.
+        stat2 = os.stat(filename)
+
+        if support.verbose:
+            print(" without access:", stat2)
+
+        # We cannot get st_dev/st_ino, so ensure those are 0 or else our test
+        # is not set up correctly
+        self.assertEqual(0, stat2.st_dev)
+        self.assertEqual(0, stat2.st_ino)
+
+        # st_mode and st_size should match (for a normal file, at least)
+        self.assertEqual(stat1.st_mode, stat2.st_mode)
+        self.assertEqual(stat1.st_size, stat2.st_size)
+
+        # st_ctime and st_mtime should be the same
+        self.assertEqual(stat1.st_ctime, stat2.st_ctime)
+        self.assertEqual(stat1.st_mtime, stat2.st_mtime)
+
+        # st_atime should be the same or later
+        self.assertGreaterEqual(stat1.st_atime, stat2.st_atime)
+
 
 @os_helper.skip_unless_symlink
 class NonLocalSymlinkTests(unittest.TestCase):
diff --git a/Misc/NEWS.d/next/Windows/2024-01-04-21-16-31.gh-issue-111877.fR-B4c.rst b/Misc/NEWS.d/next/Windows/2024-01-04-21-16-31.gh-issue-111877.fR-B4c.rst
new file mode 100644 (file)
index 0000000..99ed8d3
--- /dev/null
@@ -0,0 +1,2 @@
+:func:`os.stat` calls were returning incorrect time values for files that
+could not be accessed directly.
index 3468ab4632d811c6cdfe9613dab1edf5b95e0696..1c230619679efcc3bc2840543469b9cb97dcd1ef 100644 (file)
@@ -1864,8 +1864,9 @@ win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result,
     HANDLE hFile;
     BY_HANDLE_FILE_INFORMATION fileInfo;
     FILE_BASIC_INFO basicInfo;
+    FILE_BASIC_INFO *pBasicInfo = NULL;
     FILE_ID_INFO idInfo;
-    FILE_ID_INFO *pIdInfo = &idInfo;
+    FILE_ID_INFO *pIdInfo = NULL;
     FILE_ATTRIBUTE_TAG_INFO tagInfo = { 0 };
     DWORD fileType, error;
     BOOL isUnhandledTag = FALSE;
@@ -2016,14 +2017,17 @@ win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result,
             retval = -1;
             goto cleanup;
         }
-    }
 
-    if (!GetFileInformationByHandleEx(hFile, FileIdInfo, &idInfo, sizeof(idInfo))) {
-        /* Failed to get FileIdInfo, so do not pass it along */
-        pIdInfo = NULL;
+        /* Successfully got FileBasicInfo, so we'll pass it along */
+        pBasicInfo = &basicInfo;
+
+        if (GetFileInformationByHandleEx(hFile, FileIdInfo, &idInfo, sizeof(idInfo))) {
+            /* Successfully got FileIdInfo, so pass it along */
+            pIdInfo = &idInfo;
+        }
     }
 
-    _Py_attribute_data_to_stat(&fileInfo, tagInfo.ReparseTag, &basicInfo, pIdInfo, result);
+    _Py_attribute_data_to_stat(&fileInfo, tagInfo.ReparseTag, pBasicInfo, pIdInfo, result);
     update_st_mode_from_path(path, fileInfo.dwFileAttributes, result);
 
 cleanup: