Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion Doc/library/os.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2062,6 +2062,7 @@ features:
Although Windows supports :func:`chmod`, you can only set the file's
read-only flag with it (via the ``stat.S_IWRITE`` and ``stat.S_IREAD``
constants or a corresponding integer value). All other bits are ignored.
The default value of *follow_symlinks* on Windows is ``False``.

The function is limited on Emscripten and WASI, see
:ref:`wasm-availability` for more information.
Expand All @@ -2075,6 +2076,9 @@ features:
.. versionchanged:: 3.6
Accepts a :term:`path-like object`.

.. versionchanged:: 3.13
Added support for the *follow_symlinks* argument on Windows.


.. function:: chown(path, uid, gid, *, dir_fd=None, follow_symlinks=True)

Expand Down Expand Up @@ -2162,11 +2166,14 @@ features:

.. audit-event:: os.chmod path,mode,dir_fd os.lchmod

.. availability:: Unix.
.. availability:: Unix, Windows.

.. versionchanged:: 3.6
Accepts a :term:`path-like object`.

.. versionchanged:: 3.13
Added support on Windows.

.. function:: lchown(path, uid, gid)

Change the owner and group id of *path* to the numeric *uid* and *gid*. This
Expand Down
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,10 @@ os
CPU resources of a container system without having to modify the container (application code).
(Contributed by Donghee Na in :gh:`109595`)

* Add support of :func:`os.lchmod` and the *follow_symlinks* argument
in :func:`os.chmod` on Windows.
(Contributed by Serhiy Storchaka in :gh:`59616`)

pathlib
-------

Expand Down
1 change: 1 addition & 0 deletions Lib/os.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ def _add(str, fn):
_add("HAVE_FSTATAT", "stat")
_add("HAVE_LCHFLAGS", "chflags")
_add("HAVE_LCHMOD", "chmod")
_add("MS_WINDOWS", "chmod")
if _exists("lchown"): # mac os x10.3
_add("HAVE_LCHOWN", "chown")
_add("HAVE_LINKAT", "link")
Expand Down
2 changes: 1 addition & 1 deletion Lib/tempfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ def _dont_follow_symlinks(func, path, *args):
# Pass follow_symlinks=False, unless not supported on this platform.
if func in _os.supports_follow_symlinks:
func(path, *args, follow_symlinks=False)
elif _os.name == 'nt' or not _os.path.islink(path):
elif not _os.path.islink(path):
func(path, *args)

def _resetperms(path):
Expand Down
120 changes: 119 additions & 1 deletion Lib/test/test_posix.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@

try:
import posix
nt = None
except ImportError:
import nt as posix
import nt
posix = nt

try:
import pwd
Expand Down Expand Up @@ -935,6 +937,122 @@ def test_utime(self):
posix.utime(os_helper.TESTFN, (int(now), int(now)))
posix.utime(os_helper.TESTFN, (now, now))

def check_chmod(self, chmod_func, target, **kwargs):
mode = os.stat(target).st_mode
try:
new_mode = mode & ~(stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR)
chmod_func(target, new_mode, **kwargs)
self.assertEqual(os.stat(target).st_mode, new_mode)
if stat.S_ISREG(mode):
try:
with open(target, 'wb+'):
pass
except PermissionError:
pass
new_mode = mode | (stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR)
chmod_func(target, new_mode, **kwargs)
self.assertEqual(os.stat(target).st_mode, new_mode)
if stat.S_ISREG(mode):
with open(target, 'wb+'):
pass
finally:
posix.chmod(target, mode)

def test_chmod_file(self):
self.check_chmod(posix.chmod, os_helper.TESTFN)

def tempdir(self):
target = os_helper.TESTFN + 'd'
posix.mkdir(target)
self.addCleanup(posix.rmdir, target)
return target

def test_chmod_dir(self):
target = self.tempdir()
self.check_chmod(posix.chmod, target)

@unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()')
def test_lchmod_file(self):
self.check_chmod(posix.lchmod, os_helper.TESTFN)
self.check_chmod(posix.chmod, os_helper.TESTFN, follow_symlinks=False)

@unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()')
def test_lchmod_dir(self):
target = self.tempdir()
self.check_chmod(posix.lchmod, target)
self.check_chmod(posix.chmod, target, follow_symlinks=False)

def check_chmod_link(self, chmod_func, target, link, **kwargs):
target_mode = os.stat(target).st_mode
link_mode = os.lstat(link).st_mode
try:
new_mode = target_mode & ~(stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR)
chmod_func(link, new_mode, **kwargs)
self.assertEqual(os.stat(target).st_mode, new_mode)
self.assertEqual(os.lstat(link).st_mode, link_mode)
new_mode = target_mode | (stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR)
chmod_func(link, new_mode, **kwargs)
self.assertEqual(os.stat(target).st_mode, new_mode)
self.assertEqual(os.lstat(link).st_mode, link_mode)
finally:
posix.chmod(target, target_mode)

def check_lchmod_link(self, chmod_func, target, link, **kwargs):
target_mode = os.stat(target).st_mode
link_mode = os.lstat(link).st_mode
new_mode = link_mode & ~(stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR)
chmod_func(link, new_mode, **kwargs)
self.assertEqual(os.stat(target).st_mode, target_mode)
self.assertEqual(os.lstat(link).st_mode, new_mode)
new_mode = link_mode | (stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR)
chmod_func(link, new_mode, **kwargs)
self.assertEqual(os.stat(target).st_mode, target_mode)
self.assertEqual(os.lstat(link).st_mode, new_mode)

@os_helper.skip_unless_symlink
def test_chmod_file_symlink(self):
target = os_helper.TESTFN
link = os_helper.TESTFN + '-link'
os.symlink(target, link)
self.addCleanup(posix.unlink, link)
if os.name == 'nt':
self.check_lchmod_link(posix.chmod, target, link)
else:
self.check_chmod_link(posix.chmod, target, link)
self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True)

@os_helper.skip_unless_symlink
def test_chmod_dir_symlink(self):
target = self.tempdir()
link = os_helper.TESTFN + '-link'
os.symlink(target, link, target_is_directory=True)
self.addCleanup(posix.unlink, link)
if os.name == 'nt':
self.check_lchmod_link(posix.chmod, target, link)
else:
self.check_chmod_link(posix.chmod, target, link)
self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True)

@unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()')
@os_helper.skip_unless_symlink
def test_lchmod_file_symlink(self):
target = os_helper.TESTFN
link = os_helper.TESTFN + '-link'
os.symlink(target, link)
self.addCleanup(posix.unlink, link)
self.check_lchmod_link(posix.chmod, target, link, follow_symlinks=False)
self.check_lchmod_link(posix.lchmod, target, link)

@unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()')
@os_helper.skip_unless_symlink
def test_lchmod_dir_symlink(self):
target = self.tempdir()
link = os_helper.TESTFN + '-link'
os.symlink(target, link)
self.addCleanup(posix.unlink, link)
self.check_lchmod_link(posix.chmod, target, link, follow_symlinks=False)
self.check_lchmod_link(posix.lchmod, target, link)

def _test_chflags_regular_file(self, chflags_func, target_file, **kwargs):
st = os.stat(target_file)
self.assertTrue(hasattr(st, 'st_flags'))
Expand Down
3 changes: 2 additions & 1 deletion Lib/test/test_shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -1132,10 +1132,11 @@ def test_copymode_symlink_to_symlink(self):
os.lchmod(src_link, stat.S_IRWXO|stat.S_IRWXG)
# link to link
os.lchmod(dst_link, stat.S_IRWXO)
old_mode = os.stat(dst).st_mode
shutil.copymode(src_link, dst_link, follow_symlinks=False)
self.assertEqual(os.lstat(src_link).st_mode,
os.lstat(dst_link).st_mode)
self.assertNotEqual(os.stat(src).st_mode, os.stat(dst).st_mode)
self.assertEqual(os.stat(dst).st_mode, old_mode)
# src link - use chmod
os.lchmod(dst_link, stat.S_IRWXO)
shutil.copymode(src_link, dst, follow_symlinks=False)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add support of :func:`os.lchmod` and the *follow_symlinks* argument in
:func:`os.chmod` on Windows.
11 changes: 6 additions & 5 deletions Modules/clinic/posixmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

79 changes: 61 additions & 18 deletions Modules/posixmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -3309,6 +3309,27 @@ os_fchdir_impl(PyObject *module, int fd)
}
#endif /* HAVE_FCHDIR */

#ifdef MS_WINDOWS
# define CHMOD_DEFAULT_FOLLOW_SYMLINKS 0
#else
# define CHMOD_DEFAULT_FOLLOW_SYMLINKS 1
#endif

#ifdef MS_WINDOWS
static int
win32_lchmod(LPCWSTR path, int mode)
{
DWORD attr = GetFileAttributesW(path);
if (attr == INVALID_FILE_ATTRIBUTES) {
return 0;
}
if (mode & _S_IWRITE)
attr &= ~FILE_ATTRIBUTE_READONLY;
else
attr |= FILE_ATTRIBUTE_READONLY;
Comment thread
serhiy-storchaka marked this conversation as resolved.
Outdated
return SetFileAttributesW(path, attr);
}
#endif

/*[clinic input]
os.chmod
Expand All @@ -3331,7 +3352,8 @@ os.chmod
and path should be relative; path will then be relative to that
directory.

follow_symlinks: bool = True
follow_symlinks: bool(c_default="CHMOD_DEFAULT_FOLLOW_SYMLINKS", \
py_default="(os.name != 'nt')") = CHMOD_DEFAULT_FOLLOW_SYMLINKS
If False, and the last element of the path is a symbolic link,
chmod will modify the symbolic link itself instead of the file
the link points to.
Expand All @@ -3348,20 +3370,16 @@ dir_fd and follow_symlinks may not be implemented on your platform.
static PyObject *
os_chmod_impl(PyObject *module, path_t *path, int mode, int dir_fd,
int follow_symlinks)
/*[clinic end generated code: output=5cf6a94915cc7bff input=674a14bc998de09d]*/
/*[clinic end generated code: output=5cf6a94915cc7bff input=fcf115d174b9f3d8]*/
{
int result;

#ifdef MS_WINDOWS
DWORD attr;
#endif

#ifdef HAVE_FCHMODAT
int fchmodat_nofollow_unsupported = 0;
int fchmodat_unsupported = 0;
#endif

#if !(defined(HAVE_FCHMODAT) || defined(HAVE_LCHMOD))
#if !(defined(HAVE_FCHMODAT) || defined(HAVE_LCHMOD) || defined(MS_WINDOWS))
if (follow_symlinks_specified("chmod", follow_symlinks))
return NULL;
#endif
Expand All @@ -3372,19 +3390,34 @@ os_chmod_impl(PyObject *module, path_t *path, int mode, int dir_fd,
}

#ifdef MS_WINDOWS
result = 0;
Py_BEGIN_ALLOW_THREADS
attr = GetFileAttributesW(path->wide);
if (attr == INVALID_FILE_ATTRIBUTES)
result = 0;
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 (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;
Comment thread
serhiy-storchaka marked this conversation as resolved.
Outdated
result = SetFileInformationByHandle(hfile, FileBasicInfo,
&info, sizeof(info));
}
CloseHandle(hfile);
Comment thread
serhiy-storchaka marked this conversation as resolved.
Outdated
}
}
else {
if (mode & _S_IWRITE)
attr &= ~FILE_ATTRIBUTE_READONLY;
else
attr |= FILE_ATTRIBUTE_READONLY;
result = SetFileAttributesW(path->wide, attr);
result = win32_lchmod(path->wide, mode);
}
Py_END_ALLOW_THREADS

if (!result) {
return path_error(path);
}
Expand Down Expand Up @@ -3514,7 +3547,7 @@ os_fchmod_impl(PyObject *module, int fd, int mode)
#endif /* HAVE_FCHMOD */


#ifdef HAVE_LCHMOD
#if defined(HAVE_LCHMOD) || defined(MS_WINDOWS)
/*[clinic input]
os.lchmod

Expand All @@ -3535,16 +3568,26 @@ os_lchmod_impl(PyObject *module, path_t *path, int mode)
if (PySys_Audit("os.chmod", "Oii", path->object, mode, -1) < 0) {
return NULL;
}
#ifdef MS_WINDOWS
Py_BEGIN_ALLOW_THREADS
res = win32_lchmod(path->wide, mode);
Py_END_ALLOW_THREADS
if (!res) {
path_error(path);
Comment thread
erlend-aasland marked this conversation as resolved.
return NULL;
}
#else /* MS_WINDOWS */
Py_BEGIN_ALLOW_THREADS
res = lchmod(path->narrow, mode);
Py_END_ALLOW_THREADS
if (res < 0) {
path_error(path);
return NULL;
}
#endif /* MS_WINDOWS */
Py_RETURN_NONE;
}
#endif /* HAVE_LCHMOD */
#endif /* HAVE_LCHMOD || MS_WINDOWS */


#ifdef HAVE_CHFLAGS
Expand Down
Loading