Skip to content

Commit b698d8e

Browse files
committed
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.)
1 parent b7eb563 commit b698d8e

File tree

5 files changed

+96
-51
lines changed

5 files changed

+96
-51
lines changed

Doc/library/os.rst

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1750,14 +1750,10 @@ Files and Directories
17501750
The *dir_fd* argument.
17511751

17521752

1753-
.. function:: remove(path, *, dir_fd=None, rmdir=False)
1753+
.. function:: remove(path, *, dir_fd=None)
17541754

1755-
Remove (delete) the file *path*. This function is identical to
1756-
:func:`os.unlink`.
1757-
1758-
Specify ``rmdir=True`` if *path* is a directory. Failing to do so
1759-
will raise an exception; likewise, specifying ``rmdir=True`` when
1760-
*path* is not a directory will also raise an exception.
1755+
Remove (delete) the file *path*. If *path* is a directory, :exc:`OSError`
1756+
is raised. Use :func:`rmdir` to remove directories.
17611757

17621758
If *dir_fd* is not ``None``, it should be a file descriptor referring to a
17631759
directory, and *path* should be relative; path will then be relative to
@@ -1771,10 +1767,12 @@ Files and Directories
17711767
be raised; on Unix, the directory entry is removed but the storage allocated
17721768
to the file is not made available until the original file is no longer in use.
17731769

1770+
This function is identical to :func:`unlink`.
1771+
17741772
Availability: Unix, Windows.
17751773

17761774
.. versionadded:: 3.3
1777-
The *dir_fd* and *rmdir* arguments.
1775+
The *dir_fd* argument.
17781776

17791777

17801778
.. function:: removedirs(path)
@@ -1872,14 +1870,25 @@ Files and Directories
18721870
.. versionadded:: 3.3
18731871

18741872

1875-
.. function:: rmdir(path)
1873+
.. function:: rmdir(path, *, dir_fd=None)
18761874

18771875
Remove (delete) the directory *path*. Only works when the directory is
18781876
empty, otherwise, :exc:`OSError` is raised. In order to remove whole
18791877
directory trees, :func:`shutil.rmtree` can be used.
18801878

1879+
If *dir_fd* is not ``None``, it should be a file descriptor referring to a
1880+
directory, and *path* should be relative; path will then be relative to
1881+
that directory. (If *path* is absolute, *dir_fd* is ignored.)
1882+
*dir_fd* may not be supported on your platform;
1883+
you can check whether or not it is available using
1884+
:data:`os.supports_dir_fd`. If it is unavailable, using it will raise
1885+
a :exc:`NotImplementedError`.
1886+
18811887
Availability: Unix, Windows.
18821888

1889+
.. versionadded:: 3.3
1890+
The *dir_fd* parameter.
1891+
18831892

18841893
.. data:: XATTR_SIZE_MAX
18851894

@@ -2235,17 +2244,17 @@ Files and Directories
22352244
.. versionadded:: 3.3
22362245

22372246

2238-
.. function:: unlink(path, *, dir_fd=None, rmdir=False)
2247+
.. function:: unlink(path, *, dir_fd=None)
22392248

2240-
Remove (delete) the file *path*. This is the same function as
2249+
Remove (delete) the file *path*. This function is identical to
22412250
:func:`remove`; the :func:`unlink` name is its traditional Unix
22422251
name. Please see the documentation for :func:`remove` for
22432252
further information.
22442253

22452254
Availability: Unix, Windows.
22462255

22472256
.. versionadded:: 3.3
2248-
The *dir_fd* and *rmdir* parameters.
2257+
The *dir_fd* parameter.
22492258

22502259

22512260
.. function:: utime(path, times=None, *, ns=None, dir_fd=None, follow_symlinks=True)

Lib/os.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ def _add(str, fn):
157157
_add("HAVE_RENAMEAT", "rename")
158158
_add("HAVE_SYMLINKAT", "symlink")
159159
_add("HAVE_UNLINKAT", "unlink")
160+
_add("HAVE_UNLINKAT", "rmdir")
160161
_add("HAVE_UTIMENSAT", "utime")
161162
supports_dir_fd = _set
162163

@@ -214,10 +215,6 @@ def _add(str, fn):
214215
_add("MS_WINDOWS", "stat")
215216
supports_follow_symlinks = _set
216217

217-
_set = set()
218-
_add("HAVE_UNLINKAT", "unlink")
219-
supports_remove_directory = _set
220-
221218
del _set
222219
del _have_functions
223220
del _globals

Lib/test/test_os.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -785,7 +785,10 @@ def tearDown(self):
785785
os.unlink(name, dir_fd=rootfd)
786786
for name in dirs:
787787
st = os.stat(name, dir_fd=rootfd, follow_symlinks=False)
788-
os.unlink(name, dir_fd=rootfd, rmdir=stat.S_ISDIR(st.st_mode))
788+
if stat.S_ISDIR(st.st_mode):
789+
os.rmdir(name, dir_fd=rootfd)
790+
else:
791+
os.unlink(name, dir_fd=rootfd)
789792
os.rmdir(support.TESTFN)
790793

791794

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ Core and Builtins
4747
Library
4848
-------
4949

50+
- Issue #15154: Add "dir_fd" parameter to os.rmdir, remove "rmdir"
51+
parameter from os.remove / os.unlink.
52+
5053
- Issue #4489: Add a shutil.rmtree that isn't susceptible to symlink attacks.
5154
It is used automatically on platforms supporting the necessary os.openat()
5255
and os.unlinkat() functions. Main code by Martin von Löwis.

Modules/posixmodule.c

Lines changed: 67 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4084,17 +4084,62 @@ posix_replace(PyObject *self, PyObject *args, PyObject *kwargs)
40844084
}
40854085

40864086
PyDoc_STRVAR(posix_rmdir__doc__,
4087-
"rmdir(path)\n\n\
4088-
Remove a directory.");
4087+
"rmdir(path, *, dir_fd=None)\n\n\
4088+
Remove a directory.\n\
4089+
\n\
4090+
If dir_fd is not None, it should be a file descriptor open to a directory,\n\
4091+
and path should be relative; path will then be relative to that directory.\n\
4092+
dir_fd may not be implemented on your platform.\n\
4093+
If it is unavailable, using it will raise a NotImplementedError.");
40894094

40904095
static PyObject *
4091-
posix_rmdir(PyObject *self, PyObject *args)
4096+
posix_rmdir(PyObject *self, PyObject *args, PyObject *kwargs)
40924097
{
4098+
path_t path;
4099+
int dir_fd = DEFAULT_DIR_FD;
4100+
static char *keywords[] = {"path", "dir_fd", NULL};
4101+
int result;
4102+
PyObject *return_value = NULL;
4103+
4104+
memset(&path, 0, sizeof(path));
4105+
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&|$O&:rmdir", keywords,
4106+
path_converter, &path,
4107+
#ifdef HAVE_UNLINKAT
4108+
dir_fd_converter, &dir_fd
4109+
#else
4110+
dir_fd_unavailable, &dir_fd
4111+
#endif
4112+
))
4113+
return NULL;
4114+
4115+
Py_BEGIN_ALLOW_THREADS
40934116
#ifdef MS_WINDOWS
4094-
return win32_1str(args, "rmdir", "y:rmdir", RemoveDirectoryA, "U:rmdir", RemoveDirectoryW);
4117+
if (path.wide)
4118+
result = RemoveDirectoryW(path.wide);
4119+
else
4120+
result = RemoveDirectoryA(path.narrow);
4121+
result = !result; /* Windows, success=1, UNIX, success=0 */
40954122
#else
4096-
return posix_1str(args, "O&:rmdir", rmdir);
4123+
#ifdef HAVE_UNLINKAT
4124+
if (dir_fd != DEFAULT_DIR_FD)
4125+
result = unlinkat(dir_fd, path.narrow, AT_REMOVEDIR);
4126+
else
40974127
#endif
4128+
result = rmdir(path.narrow);
4129+
#endif
4130+
Py_END_ALLOW_THREADS
4131+
4132+
if (result) {
4133+
return_value = path_error("rmdir", &path);
4134+
goto exit;
4135+
}
4136+
4137+
return_value = Py_None;
4138+
Py_INCREF(Py_None);
4139+
4140+
exit:
4141+
path_cleanup(&path);
4142+
return return_value;
40984143
}
40994144

41004145

@@ -4186,68 +4231,54 @@ BOOL WINAPI Py_DeleteFileW(LPCWSTR lpFileName)
41864231
#endif /* MS_WINDOWS */
41874232

41884233
PyDoc_STRVAR(posix_unlink__doc__,
4189-
"unlink(path, *, dir_fd=None, rmdir=False)\n\n\
4234+
"unlink(path, *, dir_fd=None)\n\n\
41904235
Remove a file (same as remove()).\n\
41914236
\n\
41924237
If dir_fd is not None, it should be a file descriptor open to a directory,\n\
41934238
and path should be relative; path will then be relative to that directory.\n\
41944239
dir_fd may not be implemented on your platform.\n\
4195-
If it is unavailable, using it will raise a NotImplementedError.\n\
4196-
If rmdir is True, unlink will behave like os.rmdir().");
4240+
If it is unavailable, using it will raise a NotImplementedError.");
41974241

41984242
PyDoc_STRVAR(posix_remove__doc__,
4199-
"remove(path, *, dir_fd=None, rmdir=False)\n\n\
4243+
"remove(path, *, dir_fd=None)\n\n\
42004244
Remove a file (same as unlink()).\n\
42014245
\n\
42024246
If dir_fd is not None, it should be a file descriptor open to a directory,\n\
42034247
and path should be relative; path will then be relative to that directory.\n\
42044248
dir_fd may not be implemented on your platform.\n\
4205-
If it is unavailable, using it will raise a NotImplementedError.\n\
4206-
If rmdir is True, remove will behave like os.rmdir().");
4249+
If it is unavailable, using it will raise a NotImplementedError.");
42074250

42084251
static PyObject *
42094252
posix_unlink(PyObject *self, PyObject *args, PyObject *kwargs)
42104253
{
42114254
path_t path;
42124255
int dir_fd = DEFAULT_DIR_FD;
4213-
int remove_dir = 0;
4214-
static char *keywords[] = {"path", "dir_fd", "rmdir", NULL};
4256+
static char *keywords[] = {"path", "dir_fd", NULL};
42154257
int result;
42164258
PyObject *return_value = NULL;
42174259

42184260
memset(&path, 0, sizeof(path));
4219-
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&|$O&p:unlink", keywords,
4261+
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&|$O&:unlink", keywords,
42204262
path_converter, &path,
42214263
#ifdef HAVE_UNLINKAT
4222-
dir_fd_converter, &dir_fd,
4264+
dir_fd_converter, &dir_fd
42234265
#else
4224-
dir_fd_unavailable, &dir_fd,
4266+
dir_fd_unavailable, &dir_fd
42254267
#endif
4226-
&remove_dir))
4268+
))
42274269
return NULL;
42284270

42294271
Py_BEGIN_ALLOW_THREADS
42304272
#ifdef MS_WINDOWS
4231-
if (remove_dir) {
4232-
if (path.wide)
4233-
result = RemoveDirectoryW(path.wide);
4234-
else
4235-
result = RemoveDirectoryA(path.narrow);
4236-
}
4237-
else {
4238-
if (path.wide)
4239-
result = Py_DeleteFileW(path.wide);
4240-
else
4241-
result = DeleteFileA(path.narrow);
4242-
}
4273+
if (path.wide)
4274+
result = Py_DeleteFileW(path.wide);
4275+
else
4276+
result = DeleteFileA(path.narrow);
42434277
result = !result; /* Windows, success=1, UNIX, success=0 */
42444278
#else
4245-
if (remove_dir && (dir_fd == DEFAULT_DIR_FD))
4246-
result = rmdir(path.narrow);
4247-
else
42484279
#ifdef HAVE_UNLINKAT
42494280
if (dir_fd != DEFAULT_DIR_FD)
4250-
result = unlinkat(dir_fd, path.narrow, remove_dir ? AT_REMOVEDIR : 0);
4281+
result = unlinkat(dir_fd, path.narrow, 0);
42514282
else
42524283
#endif /* HAVE_UNLINKAT */
42534284
result = unlink(path.narrow);
@@ -10806,7 +10837,9 @@ static PyMethodDef posix_methods[] = {
1080610837
{"replace", (PyCFunction)posix_replace,
1080710838
METH_VARARGS | METH_KEYWORDS,
1080810839
posix_replace__doc__},
10809-
{"rmdir", posix_rmdir, METH_VARARGS, posix_rmdir__doc__},
10840+
{"rmdir", (PyCFunction)posix_rmdir,
10841+
METH_VARARGS | METH_KEYWORDS,
10842+
posix_rmdir__doc__},
1081010843
{"stat", (PyCFunction)posix_stat,
1081110844
METH_VARARGS | METH_KEYWORDS,
1081210845
posix_stat__doc__},

0 commit comments

Comments
 (0)