Issue #15154: Add "dir_fd" parameter to os.rmdir, remove "rmdir"
parameter from os.remove / os.unlink. Patch written by Georg Brandl. (I'm really looking forward to George getting commit privileges so I don't have to keep doing checkins on his behalf.)
This commit is contained in:
parent
b7eb563a2a
commit
b698d8e7e9
|
@ -1750,14 +1750,10 @@ Files and Directories
|
||||||
The *dir_fd* argument.
|
The *dir_fd* argument.
|
||||||
|
|
||||||
|
|
||||||
.. function:: remove(path, *, dir_fd=None, rmdir=False)
|
.. function:: remove(path, *, dir_fd=None)
|
||||||
|
|
||||||
Remove (delete) the file *path*. This function is identical to
|
Remove (delete) the file *path*. If *path* is a directory, :exc:`OSError`
|
||||||
:func:`os.unlink`.
|
is raised. Use :func:`rmdir` to remove directories.
|
||||||
|
|
||||||
Specify ``rmdir=True`` if *path* is a directory. Failing to do so
|
|
||||||
will raise an exception; likewise, specifying ``rmdir=True`` when
|
|
||||||
*path* is not a directory will also raise an exception.
|
|
||||||
|
|
||||||
If *dir_fd* is not ``None``, it should be a file descriptor referring to a
|
If *dir_fd* is not ``None``, it should be a file descriptor referring to a
|
||||||
directory, and *path* should be relative; path will then be relative to
|
directory, and *path* should be relative; path will then be relative to
|
||||||
|
@ -1771,10 +1767,12 @@ Files and Directories
|
||||||
be raised; on Unix, the directory entry is removed but the storage allocated
|
be raised; on Unix, the directory entry is removed but the storage allocated
|
||||||
to the file is not made available until the original file is no longer in use.
|
to the file is not made available until the original file is no longer in use.
|
||||||
|
|
||||||
|
This function is identical to :func:`unlink`.
|
||||||
|
|
||||||
Availability: Unix, Windows.
|
Availability: Unix, Windows.
|
||||||
|
|
||||||
.. versionadded:: 3.3
|
.. versionadded:: 3.3
|
||||||
The *dir_fd* and *rmdir* arguments.
|
The *dir_fd* argument.
|
||||||
|
|
||||||
|
|
||||||
.. function:: removedirs(path)
|
.. function:: removedirs(path)
|
||||||
|
@ -1872,14 +1870,25 @@ Files and Directories
|
||||||
.. versionadded:: 3.3
|
.. versionadded:: 3.3
|
||||||
|
|
||||||
|
|
||||||
.. function:: rmdir(path)
|
.. function:: rmdir(path, *, dir_fd=None)
|
||||||
|
|
||||||
Remove (delete) the directory *path*. Only works when the directory is
|
Remove (delete) the directory *path*. Only works when the directory is
|
||||||
empty, otherwise, :exc:`OSError` is raised. In order to remove whole
|
empty, otherwise, :exc:`OSError` is raised. In order to remove whole
|
||||||
directory trees, :func:`shutil.rmtree` can be used.
|
directory trees, :func:`shutil.rmtree` can be used.
|
||||||
|
|
||||||
|
If *dir_fd* is not ``None``, it should be a file descriptor referring to a
|
||||||
|
directory, and *path* should be relative; path will then be relative to
|
||||||
|
that directory. (If *path* is absolute, *dir_fd* is ignored.)
|
||||||
|
*dir_fd* may not be supported on your platform;
|
||||||
|
you can check whether or not it is available using
|
||||||
|
:data:`os.supports_dir_fd`. If it is unavailable, using it will raise
|
||||||
|
a :exc:`NotImplementedError`.
|
||||||
|
|
||||||
Availability: Unix, Windows.
|
Availability: Unix, Windows.
|
||||||
|
|
||||||
|
.. versionadded:: 3.3
|
||||||
|
The *dir_fd* parameter.
|
||||||
|
|
||||||
|
|
||||||
.. data:: XATTR_SIZE_MAX
|
.. data:: XATTR_SIZE_MAX
|
||||||
|
|
||||||
|
@ -2235,9 +2244,9 @@ Files and Directories
|
||||||
.. versionadded:: 3.3
|
.. versionadded:: 3.3
|
||||||
|
|
||||||
|
|
||||||
.. function:: unlink(path, *, dir_fd=None, rmdir=False)
|
.. function:: unlink(path, *, dir_fd=None)
|
||||||
|
|
||||||
Remove (delete) the file *path*. This is the same function as
|
Remove (delete) the file *path*. This function is identical to
|
||||||
:func:`remove`; the :func:`unlink` name is its traditional Unix
|
:func:`remove`; the :func:`unlink` name is its traditional Unix
|
||||||
name. Please see the documentation for :func:`remove` for
|
name. Please see the documentation for :func:`remove` for
|
||||||
further information.
|
further information.
|
||||||
|
@ -2245,7 +2254,7 @@ Files and Directories
|
||||||
Availability: Unix, Windows.
|
Availability: Unix, Windows.
|
||||||
|
|
||||||
.. versionadded:: 3.3
|
.. versionadded:: 3.3
|
||||||
The *dir_fd* and *rmdir* parameters.
|
The *dir_fd* parameter.
|
||||||
|
|
||||||
|
|
||||||
.. function:: utime(path, times=None, *, ns=None, dir_fd=None, follow_symlinks=True)
|
.. function:: utime(path, times=None, *, ns=None, dir_fd=None, follow_symlinks=True)
|
||||||
|
|
|
@ -157,6 +157,7 @@ if _exists("_have_functions"):
|
||||||
_add("HAVE_RENAMEAT", "rename")
|
_add("HAVE_RENAMEAT", "rename")
|
||||||
_add("HAVE_SYMLINKAT", "symlink")
|
_add("HAVE_SYMLINKAT", "symlink")
|
||||||
_add("HAVE_UNLINKAT", "unlink")
|
_add("HAVE_UNLINKAT", "unlink")
|
||||||
|
_add("HAVE_UNLINKAT", "rmdir")
|
||||||
_add("HAVE_UTIMENSAT", "utime")
|
_add("HAVE_UTIMENSAT", "utime")
|
||||||
supports_dir_fd = _set
|
supports_dir_fd = _set
|
||||||
|
|
||||||
|
@ -214,10 +215,6 @@ if _exists("_have_functions"):
|
||||||
_add("MS_WINDOWS", "stat")
|
_add("MS_WINDOWS", "stat")
|
||||||
supports_follow_symlinks = _set
|
supports_follow_symlinks = _set
|
||||||
|
|
||||||
_set = set()
|
|
||||||
_add("HAVE_UNLINKAT", "unlink")
|
|
||||||
supports_remove_directory = _set
|
|
||||||
|
|
||||||
del _set
|
del _set
|
||||||
del _have_functions
|
del _have_functions
|
||||||
del _globals
|
del _globals
|
||||||
|
|
|
@ -785,7 +785,10 @@ class FwalkTests(WalkTests):
|
||||||
os.unlink(name, dir_fd=rootfd)
|
os.unlink(name, dir_fd=rootfd)
|
||||||
for name in dirs:
|
for name in dirs:
|
||||||
st = os.stat(name, dir_fd=rootfd, follow_symlinks=False)
|
st = os.stat(name, dir_fd=rootfd, follow_symlinks=False)
|
||||||
os.unlink(name, dir_fd=rootfd, rmdir=stat.S_ISDIR(st.st_mode))
|
if stat.S_ISDIR(st.st_mode):
|
||||||
|
os.rmdir(name, dir_fd=rootfd)
|
||||||
|
else:
|
||||||
|
os.unlink(name, dir_fd=rootfd)
|
||||||
os.rmdir(support.TESTFN)
|
os.rmdir(support.TESTFN)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,9 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #15154: Add "dir_fd" parameter to os.rmdir, remove "rmdir"
|
||||||
|
parameter from os.remove / os.unlink.
|
||||||
|
|
||||||
- Issue #4489: Add a shutil.rmtree that isn't susceptible to symlink attacks.
|
- Issue #4489: Add a shutil.rmtree that isn't susceptible to symlink attacks.
|
||||||
It is used automatically on platforms supporting the necessary os.openat()
|
It is used automatically on platforms supporting the necessary os.openat()
|
||||||
and os.unlinkat() functions. Main code by Martin von Löwis.
|
and os.unlinkat() functions. Main code by Martin von Löwis.
|
||||||
|
|
|
@ -4084,17 +4084,62 @@ posix_replace(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||||
}
|
}
|
||||||
|
|
||||||
PyDoc_STRVAR(posix_rmdir__doc__,
|
PyDoc_STRVAR(posix_rmdir__doc__,
|
||||||
"rmdir(path)\n\n\
|
"rmdir(path, *, dir_fd=None)\n\n\
|
||||||
Remove a directory.");
|
Remove a directory.\n\
|
||||||
|
\n\
|
||||||
|
If dir_fd is not None, it should be a file descriptor open to a directory,\n\
|
||||||
|
and path should be relative; path will then be relative to that directory.\n\
|
||||||
|
dir_fd may not be implemented on your platform.\n\
|
||||||
|
If it is unavailable, using it will raise a NotImplementedError.");
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
posix_rmdir(PyObject *self, PyObject *args)
|
posix_rmdir(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||||
{
|
{
|
||||||
#ifdef MS_WINDOWS
|
path_t path;
|
||||||
return win32_1str(args, "rmdir", "y:rmdir", RemoveDirectoryA, "U:rmdir", RemoveDirectoryW);
|
int dir_fd = DEFAULT_DIR_FD;
|
||||||
|
static char *keywords[] = {"path", "dir_fd", NULL};
|
||||||
|
int result;
|
||||||
|
PyObject *return_value = NULL;
|
||||||
|
|
||||||
|
memset(&path, 0, sizeof(path));
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&|$O&:rmdir", keywords,
|
||||||
|
path_converter, &path,
|
||||||
|
#ifdef HAVE_UNLINKAT
|
||||||
|
dir_fd_converter, &dir_fd
|
||||||
#else
|
#else
|
||||||
return posix_1str(args, "O&:rmdir", rmdir);
|
dir_fd_unavailable, &dir_fd
|
||||||
#endif
|
#endif
|
||||||
|
))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
Py_BEGIN_ALLOW_THREADS
|
||||||
|
#ifdef MS_WINDOWS
|
||||||
|
if (path.wide)
|
||||||
|
result = RemoveDirectoryW(path.wide);
|
||||||
|
else
|
||||||
|
result = RemoveDirectoryA(path.narrow);
|
||||||
|
result = !result; /* Windows, success=1, UNIX, success=0 */
|
||||||
|
#else
|
||||||
|
#ifdef HAVE_UNLINKAT
|
||||||
|
if (dir_fd != DEFAULT_DIR_FD)
|
||||||
|
result = unlinkat(dir_fd, path.narrow, AT_REMOVEDIR);
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
result = rmdir(path.narrow);
|
||||||
|
#endif
|
||||||
|
Py_END_ALLOW_THREADS
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
return_value = path_error("rmdir", &path);
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return_value = Py_None;
|
||||||
|
Py_INCREF(Py_None);
|
||||||
|
|
||||||
|
exit:
|
||||||
|
path_cleanup(&path);
|
||||||
|
return return_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -4186,68 +4231,54 @@ BOOL WINAPI Py_DeleteFileW(LPCWSTR lpFileName)
|
||||||
#endif /* MS_WINDOWS */
|
#endif /* MS_WINDOWS */
|
||||||
|
|
||||||
PyDoc_STRVAR(posix_unlink__doc__,
|
PyDoc_STRVAR(posix_unlink__doc__,
|
||||||
"unlink(path, *, dir_fd=None, rmdir=False)\n\n\
|
"unlink(path, *, dir_fd=None)\n\n\
|
||||||
Remove a file (same as remove()).\n\
|
Remove a file (same as remove()).\n\
|
||||||
\n\
|
\n\
|
||||||
If dir_fd is not None, it should be a file descriptor open to a directory,\n\
|
If dir_fd is not None, it should be a file descriptor open to a directory,\n\
|
||||||
and path should be relative; path will then be relative to that directory.\n\
|
and path should be relative; path will then be relative to that directory.\n\
|
||||||
dir_fd may not be implemented on your platform.\n\
|
dir_fd may not be implemented on your platform.\n\
|
||||||
If it is unavailable, using it will raise a NotImplementedError.\n\
|
If it is unavailable, using it will raise a NotImplementedError.");
|
||||||
If rmdir is True, unlink will behave like os.rmdir().");
|
|
||||||
|
|
||||||
PyDoc_STRVAR(posix_remove__doc__,
|
PyDoc_STRVAR(posix_remove__doc__,
|
||||||
"remove(path, *, dir_fd=None, rmdir=False)\n\n\
|
"remove(path, *, dir_fd=None)\n\n\
|
||||||
Remove a file (same as unlink()).\n\
|
Remove a file (same as unlink()).\n\
|
||||||
\n\
|
\n\
|
||||||
If dir_fd is not None, it should be a file descriptor open to a directory,\n\
|
If dir_fd is not None, it should be a file descriptor open to a directory,\n\
|
||||||
and path should be relative; path will then be relative to that directory.\n\
|
and path should be relative; path will then be relative to that directory.\n\
|
||||||
dir_fd may not be implemented on your platform.\n\
|
dir_fd may not be implemented on your platform.\n\
|
||||||
If it is unavailable, using it will raise a NotImplementedError.\n\
|
If it is unavailable, using it will raise a NotImplementedError.");
|
||||||
If rmdir is True, remove will behave like os.rmdir().");
|
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
posix_unlink(PyObject *self, PyObject *args, PyObject *kwargs)
|
posix_unlink(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||||
{
|
{
|
||||||
path_t path;
|
path_t path;
|
||||||
int dir_fd = DEFAULT_DIR_FD;
|
int dir_fd = DEFAULT_DIR_FD;
|
||||||
int remove_dir = 0;
|
static char *keywords[] = {"path", "dir_fd", NULL};
|
||||||
static char *keywords[] = {"path", "dir_fd", "rmdir", NULL};
|
|
||||||
int result;
|
int result;
|
||||||
PyObject *return_value = NULL;
|
PyObject *return_value = NULL;
|
||||||
|
|
||||||
memset(&path, 0, sizeof(path));
|
memset(&path, 0, sizeof(path));
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&|$O&p:unlink", keywords,
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&|$O&:unlink", keywords,
|
||||||
path_converter, &path,
|
path_converter, &path,
|
||||||
#ifdef HAVE_UNLINKAT
|
#ifdef HAVE_UNLINKAT
|
||||||
dir_fd_converter, &dir_fd,
|
dir_fd_converter, &dir_fd
|
||||||
#else
|
#else
|
||||||
dir_fd_unavailable, &dir_fd,
|
dir_fd_unavailable, &dir_fd
|
||||||
#endif
|
#endif
|
||||||
&remove_dir))
|
))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
Py_BEGIN_ALLOW_THREADS
|
Py_BEGIN_ALLOW_THREADS
|
||||||
#ifdef MS_WINDOWS
|
#ifdef MS_WINDOWS
|
||||||
if (remove_dir) {
|
if (path.wide)
|
||||||
if (path.wide)
|
result = Py_DeleteFileW(path.wide);
|
||||||
result = RemoveDirectoryW(path.wide);
|
else
|
||||||
else
|
result = DeleteFileA(path.narrow);
|
||||||
result = RemoveDirectoryA(path.narrow);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (path.wide)
|
|
||||||
result = Py_DeleteFileW(path.wide);
|
|
||||||
else
|
|
||||||
result = DeleteFileA(path.narrow);
|
|
||||||
}
|
|
||||||
result = !result; /* Windows, success=1, UNIX, success=0 */
|
result = !result; /* Windows, success=1, UNIX, success=0 */
|
||||||
#else
|
#else
|
||||||
if (remove_dir && (dir_fd == DEFAULT_DIR_FD))
|
|
||||||
result = rmdir(path.narrow);
|
|
||||||
else
|
|
||||||
#ifdef HAVE_UNLINKAT
|
#ifdef HAVE_UNLINKAT
|
||||||
if (dir_fd != DEFAULT_DIR_FD)
|
if (dir_fd != DEFAULT_DIR_FD)
|
||||||
result = unlinkat(dir_fd, path.narrow, remove_dir ? AT_REMOVEDIR : 0);
|
result = unlinkat(dir_fd, path.narrow, 0);
|
||||||
else
|
else
|
||||||
#endif /* HAVE_UNLINKAT */
|
#endif /* HAVE_UNLINKAT */
|
||||||
result = unlink(path.narrow);
|
result = unlink(path.narrow);
|
||||||
|
@ -10806,7 +10837,9 @@ static PyMethodDef posix_methods[] = {
|
||||||
{"replace", (PyCFunction)posix_replace,
|
{"replace", (PyCFunction)posix_replace,
|
||||||
METH_VARARGS | METH_KEYWORDS,
|
METH_VARARGS | METH_KEYWORDS,
|
||||||
posix_replace__doc__},
|
posix_replace__doc__},
|
||||||
{"rmdir", posix_rmdir, METH_VARARGS, posix_rmdir__doc__},
|
{"rmdir", (PyCFunction)posix_rmdir,
|
||||||
|
METH_VARARGS | METH_KEYWORDS,
|
||||||
|
posix_rmdir__doc__},
|
||||||
{"stat", (PyCFunction)posix_stat,
|
{"stat", (PyCFunction)posix_stat,
|
||||||
METH_VARARGS | METH_KEYWORDS,
|
METH_VARARGS | METH_KEYWORDS,
|
||||||
posix_stat__doc__},
|
posix_stat__doc__},
|
||||||
|
|
Loading…
Reference in New Issue