Skip to content

Commit 20fa1e2

Browse files
committed
gh-157889: minor changes to the implementation of os.readlink.
* Simplified/unified error handling in case reading fds referring to symlinks is not supported. * Docs: centralized the information on the return type of os.readlink in a single paragraph. * Docs/comments: rewrite from "MacOS" to "macOS" (lower-case).
1 parent 253c3b4 commit 20fa1e2

6 files changed

Lines changed: 32 additions & 45 deletions

File tree

‎Doc/library/os.rst‎

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2799,24 +2799,23 @@ features:
27992799
may be converted to an absolute pathname using
28002800
``os.path.join(os.path.dirname(path), result)``.
28012801

2802-
If the *path* is a string object (directly or indirectly through a
2803-
:class:`PathLike` interface), the result will also be a string object,
2804-
and the call may raise a UnicodeDecodeError. If the *path* is a bytes
2805-
object (direct or indirectly), the result will be a bytes object.
2806-
28072802
This function can also support :ref:`paths relative to directory descriptors
28082803
<dir_fd>`.
28092804

2810-
On Linux, Android and MacOS, *path* can also be a file descriptor referring
2811-
to a symbolic link. In that case, *dir_fd* must be ``None``, and the return
2812-
value will be a string.
2813-
(On Linux and Android, such a file descriptor can be obtained through
2814-
:func:`os.open` with ``os.RDONLY | os.O_PATH | os.O_NOFOLLOW``.
2815-
On MacOS, this is possible by calling :func:`os.open` with
2805+
On Linux, Android and macOS, *path* can also be a file descriptor referring
2806+
to a symbolic link. In that case, *dir_fd* must be ``None``.
2807+
(On Linux and Android, such a file descriptor must be obtained through
2808+
:func:`os.open` with ``os.O_RDONLY | os.O_PATH | os.O_NOFOLLOW``.
2809+
On macOS, :func:`os.open` must be called with
28162810
``os.O_RDONLY | os.O_SYMLINK``.)
28172811
On other operating systems, a ``NotImplementedError`` is raised if *path*
28182812
is an integer.
28192813

2814+
If the *path* is a string object (directly or indirectly through a
2815+
:class:`PathLike` interface) or a file descriptor, the result will be a
2816+
string object, and the call may raise a UnicodeDecodeError. If the *path*
2817+
is a bytes object (direct or indirectly), the result will be a bytes object.
2818+
28202819
When trying to resolve a path that may contain links, use
28212820
:func:`~os.path.realpath` to properly handle recursion and platform
28222821
differences.
@@ -2839,9 +2838,9 @@ features:
28392838
substitution path (which typically includes ``\\?\`` prefix) rather
28402839
than the optional "print name" field that was previously returned.
28412840

2842-
.. versionchanged:: 3.16
2841+
.. versionchanged:: next
28432842
Accepts file descriptors pointing to symbolic links as *path* on
2844-
Linux, Android and MacOS.
2843+
Linux, Android and macOS.
28452844

28462845
.. function:: remove(path, *, dir_fd=None)
28472846

‎Doc/whatsnew/3.16.rst‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ os
510510
(Contributed by Md Arif in :gh:`152936`.)
511511

512512
* :func:`os.readlink` now accepts a file descriptor referring to a symlink on
513-
Linux, Android and MacOS.
513+
Linux, Android and macOS.
514514
(Contributed by OOTS in :gh:`157899`.)
515515

516516

‎Lib/test/test_os/test_posix.py‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1883,7 +1883,7 @@ def test_readlink_dir_fd(self):
18831883

18841884

18851885
_support_readlink_with_fd = hasattr(os, 'readlink') and (
1886-
"HAVE_FREADLINK" in posix._have_functions # MacOS
1886+
"HAVE_FREADLINK" in posix._have_functions # macOS
18871887
or (
18881888
os.readlink in os.supports_dir_fd
18891889
and sys.platform in ["linux", "android"]
@@ -1892,7 +1892,7 @@ def test_readlink_dir_fd(self):
18921892

18931893
def _open_symlink_as_fd(self, path):
18941894
open_flags = os.O_RDONLY
1895-
if hasattr(os, "O_SYMLINK"): # MacOS
1895+
if hasattr(os, "O_SYMLINK"): # macOS
18961896
open_flags |= os.O_SYMLINK
18971897
elif hasattr(os, "O_NOFOLLOW") and hasattr(os, "O_PATH"): # Linux
18981898
open_flags |= os.O_NOFOLLOW | os.O_PATH
@@ -1918,7 +1918,7 @@ def test_readlink_with_fd_not_referring_to_symlink_throws(self):
19181918
self.addCleanup(os.close, fd)
19191919
# on Linux/Android, readlinkat("", fd, ...) fails with ENOENT, which
19201920
# Python translates to a FileNotFoundError, a subclass of OSError.
1921-
# On MacOS, freadlink(fd, ...) fails with EINVAL, which gets raised
1921+
# On macOS, freadlink(fd, ...) fails with EINVAL, which gets raised
19221922
# as a OSError.
19231923
# So catching OSError here covers both cases.
19241924
with self.assertRaises(OSError):
@@ -2698,8 +2698,8 @@ def test_freadlink(self):
26982698

26992699
else:
27002700
self.assertNotIn("HAVE_FREADLINK", posix._have_functions)
2701-
2702-
with self.assertRaisesRegex(NotImplementedError, "readlink cannot read file descriptors on this platform"):
2701+
errmsg = "readlink cannot read file descriptors on this platform"
2702+
with self.assertRaisesRegex(NotImplementedError, errmsg):
27032703
os.readlink(0)
27042704

27052705
def test_symlink(self):
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
:func:`os.readlink` now accepts a file descriptor referring to a
2-
symlink on Linux, Android and MacOS.
2+
symlink on Linux, Android and macOS.

‎Modules/clinic/posixmodule.c.h‎

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Modules/posixmodule.c‎

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11016,22 +11016,22 @@ that directory.
1101611016
dir_fd may not be implemented on your platform. If it is unavailable,
1101711017
using it will raise a NotImplementedError.
1101811018

11019-
On Linux, Android and MacOS, path may be a file descriptor referring to
11019+
On Linux, Android and macOS, path may be a file descriptor referring to
1102011020
a symlink. If it is, dir_fd must be None, and the return value will be a
1102111021
string object. (File descriptors for symlinks can be obtained with
1102211022

1102311023
os.open(..., os.O_RDONLY | os.O_PATH | os.O_NOFOLLOW)
1102411024

11025-
on Linux and Android, and
11025+
on Linux and Android, and:
1102611026

1102711027
os.open(..., os.O_RDONLY | os.O_SYMLINK)
1102811028

11029-
on MacOS.)
11029+
on macOS.)
1103011030
[clinic start generated code]*/
1103111031

1103211032
static PyObject *
1103311033
os_readlink_impl(PyObject *module, path_t *path, int dir_fd)
11034-
/*[clinic end generated code: output=d21b732a2e814030 input=eda43153b2f38ee6]*/
11034+
/*[clinic end generated code: output=d21b732a2e814030 input=14546747b3db62f6]*/
1103511035
{
1103611036
#if defined(HAVE_READLINK)
1103711037
char buffer[MAXPATHLEN+1];
@@ -11047,33 +11047,21 @@ os_readlink_impl(PyObject *module, path_t *path, int dir_fd)
1104711047
Py_BEGIN_ALLOW_THREADS
1104811048
length = freadlink(path->fd, buffer, MAXPATHLEN);
1104911049
Py_END_ALLOW_THREADS
11050-
} else {
11051-
PyErr_SetString(PyExc_NotImplementedError,
11052-
"readlink cannot read file descriptors on this platform, "
11053-
"freadlink() is unavailable");
11054-
return NULL;
11055-
}
11050+
} else
1105611051
#elif defined(HAVE_READLINKAT) && defined(_Py_READLINKAT_SUPPORTS_EMPTY_PATH)
1105711052
// linux/android:
1105811053
// readlinkat(fd, "", ...) reads the link that fd refers to.
1105911054
if (HAVE_READLINKAT_RUNTIME) {
1106011055
Py_BEGIN_ALLOW_THREADS
1106111056
length = readlinkat(path->fd, "", buffer, MAXPATHLEN);
1106211057
Py_END_ALLOW_THREADS
11063-
} else {
11064-
// this should be unreachable:
11065-
// HAVE_READLINKAT_RUNTIME is always 1 on Linux/Android.
11066-
// Leaving it here as a safeguard.
11058+
} else
11059+
#endif
11060+
{
1106711061
PyErr_SetString(PyExc_NotImplementedError,
11068-
"readlink cannot read file descriptors on this platform, "
11069-
"readlinkat() is unavailable");
11062+
"readlink cannot read file descriptors on this platform");
1107011063
return NULL;
1107111064
}
11072-
#else
11073-
PyErr_SetString(PyExc_NotImplementedError,
11074-
"readlink cannot read file descriptors on this platform");
11075-
return NULL;
11076-
#endif
1107711065
} else
1107811066
#ifdef HAVE_READLINKAT
1107911067
if (dir_fd != DEFAULT_DIR_FD) {

0 commit comments

Comments
 (0)