]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-113191: Add support of os.fchmod() on Windows (GH-113192)
authorSerhiy Storchaka <storchaka@gmail.com>
Sun, 24 Dec 2023 10:57:11 +0000 (12:57 +0200)
committerGitHub <noreply@github.com>
Sun, 24 Dec 2023 10:57:11 +0000 (10:57 +0000)
Also support a file descriptor in os.chmod().

Doc/library/os.rst
Doc/whatsnew/3.13.rst
Lib/os.py
Lib/test/test_posix.py
Misc/NEWS.d/next/Library/2023-12-15-21-33-42.gh-issue-113191.Il155b.rst [new file with mode: 0644]
Modules/clinic/posixmodule.c.h
Modules/posixmodule.c

index 1138cc1f249ee78d2b4fbcae4b1118a6b0c2ff2c..6b6e62a683ab18b1a013e80d20417136cd93c7ff 100644 (file)
@@ -1001,11 +1001,14 @@ as internal buffering of data.
 
    .. audit-event:: os.chmod path,mode,dir_fd os.fchmod
 
-   .. availability:: Unix.
+   .. availability:: Unix, Windows.
 
       The function is limited on Emscripten and WASI, see
       :ref:`wasm-availability` for more information.
 
+   .. versionchanged:: 3.13
+      Added support on Windows.
+
 
 .. function:: fchown(fd, uid, gid)
 
@@ -2077,7 +2080,8 @@ features:
       Accepts a :term:`path-like object`.
 
    .. versionchanged:: 3.13
-      Added support for the *follow_symlinks* argument on Windows.
+      Added support for a file descriptor and the *follow_symlinks* argument
+      on Windows.
 
 
 .. function:: chown(path, uid, gid, *, dir_fd=None, follow_symlinks=True)
index 7dc02dacdc68f7434b44be239107127737db8216..b4cd106f5cac5fbc0a0abe947c5794e1e3b2ba0d 100644 (file)
@@ -289,6 +289,10 @@ os
   ``False`` on Windows.
   (Contributed by Serhiy Storchaka in :gh:`59616`)
 
+* Add support of :func:`os.fchmod` and a file descriptor
+  in :func:`os.chmod` on Windows.
+  (Contributed by Serhiy Storchaka in :gh:`113191`)
+
 * :func:`os.posix_spawn` now accepts ``env=None``, which makes the newly spawned
   process use the current process environment.
   (Contributed by Jakub Kulik in :gh:`113119`.)
index 8c4b93250918eb67cac3bff1ed1cc365995a0953..7f38e14e7bdd9603fef0a647083483fe782aaf84 100644 (file)
--- a/Lib/os.py
+++ b/Lib/os.py
@@ -131,6 +131,7 @@ if _exists("_have_functions"):
     _set = set()
     _add("HAVE_FCHDIR",     "chdir")
     _add("HAVE_FCHMOD",     "chmod")
+    _add("MS_WINDOWS",      "chmod")
     _add("HAVE_FCHOWN",     "chown")
     _add("HAVE_FDOPENDIR",  "listdir")
     _add("HAVE_FDOPENDIR",  "scandir")
index 55cc5e4c6e4f038c04d46c386205244f13c60148..9c382ace806e0f8c5b129c96baa9dec4325b47fd 100644 (file)
@@ -936,6 +936,7 @@ class PosixTester(unittest.TestCase):
         posix.utime(os_helper.TESTFN, (now, now))
 
     def check_chmod(self, chmod_func, target, **kwargs):
+        closefd = not isinstance(target, int)
         mode = os.stat(target).st_mode
         try:
             new_mode = mode & ~(stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR)
@@ -943,7 +944,7 @@ class PosixTester(unittest.TestCase):
             self.assertEqual(os.stat(target).st_mode, new_mode)
             if stat.S_ISREG(mode):
                 try:
-                    with open(target, 'wb+'):
+                    with open(target, 'wb+', closefd=closefd):
                         pass
                 except PermissionError:
                     pass
@@ -951,10 +952,10 @@ class PosixTester(unittest.TestCase):
             chmod_func(target, new_mode, **kwargs)
             self.assertEqual(os.stat(target).st_mode, new_mode)
             if stat.S_ISREG(mode):
-                with open(target, 'wb+'):
+                with open(target, 'wb+', closefd=closefd):
                     pass
         finally:
-            posix.chmod(target, mode)
+            chmod_func(target, mode)
 
     @os_helper.skip_unless_working_chmod
     def test_chmod_file(self):
@@ -971,6 +972,12 @@ class PosixTester(unittest.TestCase):
         target = self.tempdir()
         self.check_chmod(posix.chmod, target)
 
+    @os_helper.skip_unless_working_chmod
+    def test_fchmod_file(self):
+        with open(os_helper.TESTFN, 'wb+') as f:
+            self.check_chmod(posix.fchmod, f.fileno())
+            self.check_chmod(posix.chmod, f.fileno())
+
     @unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()')
     def test_lchmod_file(self):
         self.check_chmod(posix.lchmod, os_helper.TESTFN)
diff --git a/Misc/NEWS.d/next/Library/2023-12-15-21-33-42.gh-issue-113191.Il155b.rst b/Misc/NEWS.d/next/Library/2023-12-15-21-33-42.gh-issue-113191.Il155b.rst
new file mode 100644 (file)
index 0000000..13fe4ff
--- /dev/null
@@ -0,0 +1,2 @@
+Add support of :func:`os.fchmod` and a file descriptor in :func:`os.chmod`
+on Windows.
index f36872a1eb7a0f39bf862e0378b642fd2cb65a5d..b7639af4b78a9da827ba6db457f53d24ca459753 100644 (file)
@@ -601,7 +601,7 @@ exit:
     return return_value;
 }
 
-#if defined(HAVE_FCHMOD)
+#if (defined(HAVE_FCHMOD) || defined(MS_WINDOWS))
 
 PyDoc_STRVAR(os_fchmod__doc__,
 "fchmod($module, /, fd, mode)\n"
@@ -676,7 +676,7 @@ exit:
     return return_value;
 }
 
-#endif /* defined(HAVE_FCHMOD) */
+#endif /* (defined(HAVE_FCHMOD) || defined(MS_WINDOWS)) */
 
 #if (defined(HAVE_LCHMOD) || defined(MS_WINDOWS))
 
@@ -12422,4 +12422,4 @@ os__supports_virtual_terminal(PyObject *module, PyObject *Py_UNUSED(ignored))
 #ifndef OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF
     #define OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF
 #endif /* !defined(OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF) */
-/*[clinic end generated code: output=1be15e60a553b40d input=a9049054013a1b77]*/
+/*[clinic end generated code: output=b82391c4f58231b6 input=a9049054013a1b77]*/
index c7ee591f30c51f8adf75ffcb705232e7ec46c1f0..c635fd4d993d57dd6837852677379d1ab30ece2b 100644 (file)
@@ -2855,6 +2855,8 @@ FTRUNCATE
 #ifdef MS_WINDOWS
     #undef PATH_HAVE_FTRUNCATE
     #define PATH_HAVE_FTRUNCATE 1
+    #undef PATH_HAVE_FCHMOD
+    #define PATH_HAVE_FCHMOD 1
 #endif
 
 /*[python input]
@@ -3332,7 +3334,38 @@ win32_lchmod(LPCWSTR path, int mode)
     }
     return SetFileAttributesW(path, attr);
 }
-#endif
+
+static int
+win32_hchmod(HANDLE hfile, int mode)
+{
+    FILE_BASIC_INFO info;
+    if (!GetFileInformationByHandleEx(hfile, FileBasicInfo,
+                                      &info, sizeof(info)))
+    {
+        return 0;
+    }
+    if (mode & _S_IWRITE) {
+        info.FileAttributes &= ~FILE_ATTRIBUTE_READONLY;
+    }
+    else {
+        info.FileAttributes |= FILE_ATTRIBUTE_READONLY;
+    }
+    return SetFileInformationByHandle(hfile, FileBasicInfo,
+                                      &info, sizeof(info));
+}
+
+static int
+win32_fchmod(int fd, int mode)
+{
+    HANDLE hfile = _Py_get_osfhandle_noraise(fd);
+    if (hfile == INVALID_HANDLE_VALUE) {
+        SetLastError(ERROR_INVALID_HANDLE);
+        return 0;
+    }
+    return win32_hchmod(hfile, mode);
+}
+
+#endif /* MS_WINDOWS */
 
 /*[clinic input]
 os.chmod
@@ -3395,27 +3428,16 @@ os_chmod_impl(PyObject *module, path_t *path, int mode, int dir_fd,
 #ifdef MS_WINDOWS
     result = 0;
     Py_BEGIN_ALLOW_THREADS
-    if (follow_symlinks) {
-        HANDLE hfile;
-        FILE_BASIC_INFO info;
-
-        hfile = CreateFileW(path->wide,
-                            FILE_READ_ATTRIBUTES|FILE_WRITE_ATTRIBUTES,
-                            0, NULL,
-                            OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+    if (path->fd != -1) {
+        result = win32_fchmod(path->fd, mode);
+    }
+    else if (follow_symlinks) {
+        HANDLE hfile = CreateFileW(path->wide,
+                                   FILE_READ_ATTRIBUTES|FILE_WRITE_ATTRIBUTES,
+                                   0, NULL,
+                                   OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
         if (hfile != INVALID_HANDLE_VALUE) {
-            if (GetFileInformationByHandleEx(hfile, FileBasicInfo,
-                                             &info, sizeof(info)))
-            {
-                if (mode & _S_IWRITE) {
-                    info.FileAttributes &= ~FILE_ATTRIBUTE_READONLY;
-                }
-                else {
-                    info.FileAttributes |= FILE_ATTRIBUTE_READONLY;
-                }
-                result = SetFileInformationByHandle(hfile, FileBasicInfo,
-                                                    &info, sizeof(info));
-            }
+            result = win32_hchmod(hfile, mode);
             (void)CloseHandle(hfile);
         }
     }
@@ -3511,7 +3533,7 @@ os_chmod_impl(PyObject *module, path_t *path, int mode, int dir_fd,
 }
 
 
-#ifdef HAVE_FCHMOD
+#if defined(HAVE_FCHMOD) || defined(MS_WINDOWS)
 /*[clinic input]
 os.fchmod
 
@@ -3533,12 +3555,21 @@ os_fchmod_impl(PyObject *module, int fd, int mode)
 /*[clinic end generated code: output=afd9bc05b4e426b3 input=b5594618bbbc22df]*/
 {
     int res;
-    int async_err = 0;
 
     if (PySys_Audit("os.chmod", "iii", fd, mode, -1) < 0) {
         return NULL;
     }
 
+#ifdef MS_WINDOWS
+    res = 0;
+    Py_BEGIN_ALLOW_THREADS
+    res = win32_fchmod(fd, mode);
+    Py_END_ALLOW_THREADS
+    if (!res) {
+        return PyErr_SetFromWindowsErr(0);
+    }
+#else /* MS_WINDOWS */
+    int async_err = 0;
     do {
         Py_BEGIN_ALLOW_THREADS
         res = fchmod(fd, mode);
@@ -3546,10 +3577,11 @@ os_fchmod_impl(PyObject *module, int fd, int mode)
     } while (res != 0 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
     if (res != 0)
         return (!async_err) ? posix_error() : NULL;
+#endif /* MS_WINDOWS */
 
     Py_RETURN_NONE;
 }
-#endif /* HAVE_FCHMOD */
+#endif /* HAVE_FCHMOD || MS_WINDOWS */
 
 
 #if defined(HAVE_LCHMOD) || defined(MS_WINDOWS)