diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -321,29 +321,33 @@ Directory and files operations Return disk usage statistics about the given path as a :term:`named tuple` with the attributes *total*, *used* and *free*, which are the amount of total, used and free space, in bytes. .. versionadded:: 3.3 Availability: Unix, Windows. -.. function:: chown(path, user=None, group=None) +.. function:: chown(path, user=None, group=None, dir_fd=None, \ + follow_symlinks=True) Change owner *user* and/or *group* of the given *path*. *user* can be a system user name or a uid; the same applies to *group*. At least one argument is required. See also :func:`os.chown`, the underlying function. Availability: Unix. .. versionadded:: 3.3 + .. versionchanged:: 3.5 + Added *dir_fd* and *follow_symlinks* parameters. + .. function:: which(cmd, mode=os.F_OK | os.X_OK, path=None) Return the path to an executable which would be run if the given *cmd* was called. If no *cmd* would be called, return ``None``. *mode* is a permission mask passed a to :func:`os.access`, by default determining if the file exists and executable. diff --git a/Lib/shutil.py b/Lib/shutil.py --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -967,17 +967,17 @@ elif os.name == 'nt': Returned values is a named tuple with attributes 'total', 'used' and 'free', which are the amount of total, used and free space, in bytes. """ total, free = nt._getdiskusage(path) used = total - free return _ntuple_diskusage(total, used, free) -def chown(path, user=None, group=None): +def chown(path, user=None, group=None, dir_fd=None, follow_symlinks=True): """Change owner user and group of the given path. user and group can be the uid/gid or the user/group names, and in that case, they are converted to their respective uid/gid. """ if user is None and group is None: raise ValueError("user and/or group must be set") @@ -996,17 +996,18 @@ def chown(path, user=None, group=None): if group is None: _group = -1 elif not isinstance(group, int): _group = _get_gid(group) if _group is None: raise LookupError("no such group: {!r}".format(group)) - os.chown(path, _user, _group) + os.chown(path, _user, _group, dir_fd=dir_fd, + follow_symlinks=follow_symlinks) def get_terminal_size(fallback=(80, 24)): """Get the size of the terminal window. For each of the two dimensions, the environment variable, COLUMNS and LINES respectively, is checked. If the variable is defined and the value is a positive integer, it is used. diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1214,16 +1214,17 @@ class TestShutil(unittest.TestCase): @unittest.skipUnless(UID_GID_SUPPORT, "Requires grp and pwd support") @unittest.skipUnless(hasattr(os, 'chown'), 'requires os.chown') def test_chown(self): # cleaned-up automatically by TestShutil.tearDown method dirname = self.mkdtemp() filename = tempfile.mktemp(dir=dirname) + dirfd = os.open(dirname, os.O_RDONLY) write_file(filename, 'testing chown function') with self.assertRaises(ValueError): shutil.chown(filename) with self.assertRaises(LookupError): shutil.chown(filename, user='non-exising username') @@ -1235,39 +1236,56 @@ class TestShutil(unittest.TestCase): with self.assertRaises(TypeError): shutil.chown(filename, 3.14) uid = os.getuid() gid = os.getgid() def check_chown(path, uid=None, gid=None): - s = os.stat(filename) + s = os.stat(path) if uid is not None: self.assertEqual(uid, s.st_uid) if gid is not None: self.assertEqual(gid, s.st_gid) shutil.chown(filename, uid, gid) check_chown(filename, uid, gid) shutil.chown(filename, uid) check_chown(filename, uid) shutil.chown(filename, user=uid) check_chown(filename, uid) shutil.chown(filename, group=gid) check_chown(filename, gid=gid) + shutil.chown(os.path.basename(filename), dir_fd=dirfd) + check_chown(filename, gid=gid) + shutil.chown(os.path.basename(filename), user=uid, dir_fd=dirfd) + check_chown(filename, gid=gid) + shutil.chown(os.path.basename(filename), group=gid, dir_fd=dirfd) + check_chown(filename, gid=gid) + + with self.assertRaises(FileNotFoundError): + shutil.chown('invalid-file', user=uid, dir_fd=dirfd) + shutil.chown('invalid-file', group=gid, dir_fd=dirfd) + shutil.chown('invalid-file', user=uid, group=gid, dir_fd=dirfd) shutil.chown(dirname, uid, gid) check_chown(dirname, uid, gid) shutil.chown(dirname, uid) check_chown(dirname, uid) shutil.chown(dirname, user=uid) check_chown(dirname, uid) + shutil.chown(dirname, user=uid, follow_symlinks=True) + check_chown(dirname, uid) shutil.chown(dirname, group=gid) check_chown(dirname, gid=gid) + shutil.chown(dirname, group=gid, follow_symlinks=True) + check_chown(dirname, gid=gid) + shutil.chown(dirname, user=uid, group=gid, follow_symlinks=True) + check_chown(dirname, uid=uid, gid=gid) user = pwd.getpwuid(uid)[0] group = grp.getgrgid(gid)[0] shutil.chown(filename, user, group) check_chown(filename, uid, gid) shutil.chown(dirname, user, group) check_chown(dirname, uid, gid)