@@ -505,6 +505,7 @@ static const unsigned int _Py_STATX_KNOWN = (STATX_BASIC_STATS | STATX_BTIME
505505# define HAVE_UNLINKAT_RUNTIME __builtin_available(macOS 10.10, iOS 8.0, *)
506506# define HAVE_OPENAT_RUNTIME __builtin_available(macOS 10.10, iOS 8.0, *)
507507# define HAVE_READLINKAT_RUNTIME __builtin_available(macOS 10.10, iOS 8.0, *)
508+ # define HAVE_FREADLINK_RUNTIME __builtin_available(macOS 13.0, iOS 16.0, *)
508509# define HAVE_SYMLINKAT_RUNTIME __builtin_available(macOS 10.10, iOS 8.0, *)
509510# define HAVE_FUTIMENS_RUNTIME __builtin_available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *)
510511# define HAVE_UTIMENSAT_RUNTIME __builtin_available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *)
@@ -571,6 +572,10 @@ static const unsigned int _Py_STATX_KNOWN = (STATX_BASIC_STATS | STATX_BTIME
571572# define HAVE_READLINKAT_RUNTIME (readlinkat != NULL)
572573# endif
573574
575+ # ifdef _Py_HAVE_FREADLINK
576+ # define HAVE_FREADLINK_RUNTIME (freadlink != NULL)
577+ # endif
578+
574579# ifdef HAVE_SYMLINKAT
575580# define HAVE_SYMLINKAT_RUNTIME (symlinkat != NULL)
576581# endif
@@ -10979,11 +10984,19 @@ os_unshare_impl(PyObject *module, int flags)
1097910984#endif
1098010985
1098110986
10987+
1098210988#if defined(HAVE_READLINK) || defined(MS_WINDOWS)
10989+
10990+ #if (defined(__linux__) || defined(__ANDROID__)) && defined(O_PATH)
10991+ // readlinkat(fd, "", ...) reads the symlink that fd refers to.
10992+ // supported since Linux 2.6.39 (same version that O_PATH was introduced).
10993+ #define _Py_READLINKAT_SUPPORTS_EMPTY_PATH
10994+ #endif
10995+
1098310996/*[clinic input]
1098410997os.readlink
1098510998
10986- path: path_t
10999+ path: path_t(allow_fd=True)
1098711000 *
1098811001 dir_fd: dir_fd(requires='readlinkat') = None
1098911002
@@ -10995,48 +11008,74 @@ that directory.
1099511008
1099611009dir_fd may not be implemented on your platform. If it is unavailable,
1099711010using it will raise a NotImplementedError.
11011+
11012+ On Linux, Android, macOS and iOS, path may be a file descriptor
11013+ referring to a symlink. If it is, dir_fd must be None.
1099811014[clinic start generated code]*/
1099911015
1100011016static PyObject *
1100111017os_readlink_impl(PyObject *module, path_t *path, int dir_fd)
11002- /*[clinic end generated code: output=d21b732a2e814030 input=03d10130870dbca8 ]*/
11018+ /*[clinic end generated code: output=d21b732a2e814030 input=d90bddfeee86f673 ]*/
1100311019{
1100411020#if defined(HAVE_READLINK)
1100511021 char buffer[MAXPATHLEN+1];
1100611022 ssize_t length;
11007- #ifdef HAVE_READLINKAT
11008- int readlinkat_unavailable = 0;
11009- #endif
1101011023
11011- Py_BEGIN_ALLOW_THREADS
11024+ if (path_and_dir_fd_invalid("readlink", path, dir_fd)) {
11025+ return NULL;
11026+ }
11027+
11028+ if (path->is_fd) {
11029+ #if defined(_Py_HAVE_FREADLINK)
11030+ if (HAVE_FREADLINK_RUNTIME) {
11031+ Py_BEGIN_ALLOW_THREADS
11032+ length = freadlink(path->fd, buffer, MAXPATHLEN);
11033+ Py_END_ALLOW_THREADS
11034+ } else
11035+ #elif defined(HAVE_READLINKAT) && defined(_Py_READLINKAT_SUPPORTS_EMPTY_PATH)
11036+ // linux/android:
11037+ // readlinkat(fd, "", ...) reads the link that fd refers to.
11038+ if (HAVE_READLINKAT_RUNTIME) {
11039+ Py_BEGIN_ALLOW_THREADS
11040+ length = readlinkat(path->fd, "", buffer, MAXPATHLEN);
11041+ Py_END_ALLOW_THREADS
11042+ } else
11043+ #endif
11044+ {
11045+ PyErr_SetString(PyExc_NotImplementedError,
11046+ "readlink cannot read file descriptors on this platform");
11047+ return NULL;
11048+ }
11049+ } else
1101211050#ifdef HAVE_READLINKAT
1101311051 if (dir_fd != DEFAULT_DIR_FD) {
1101411052 if (HAVE_READLINKAT_RUNTIME) {
11053+ Py_BEGIN_ALLOW_THREADS
1101511054 length = readlinkat(dir_fd, path->narrow, buffer, MAXPATHLEN);
11055+ Py_END_ALLOW_THREADS
1101611056 } else {
11017- readlinkat_unavailable = 1;
11057+ argument_unavailable_error(NULL, "dir_fd");
11058+ return NULL;
1101811059 }
1101911060 } else
1102011061#endif
11062+ {
11063+ Py_BEGIN_ALLOW_THREADS
1102111064 length = readlink(path->narrow, buffer, MAXPATHLEN);
11022- Py_END_ALLOW_THREADS
11023-
11024- #ifdef HAVE_READLINKAT
11025- if (readlinkat_unavailable) {
11026- argument_unavailable_error(NULL, "dir_fd");
11027- return NULL;
11065+ Py_END_ALLOW_THREADS
1102811066 }
11029- #endif
1103011067
1103111068 if (length < 0) {
1103211069 return path_error(path);
1103311070 }
1103411071 buffer[length] = '\0';
1103511072
11036- if (PyUnicode_Check(path->object))
11073+ if (path->is_fd || PyUnicode_Check(path->object)) {
1103711074 return PyUnicode_DecodeFSDefaultAndSize(buffer, length);
11038- else
11075+ }
11076+ else {
1103911077 return PyBytes_FromStringAndSize(buffer, length);
11078+ }
1104011079#elif defined(MS_WINDOWS)
1104111080 DWORD n_bytes_returned;
1104211081 DWORD io_result = 0;
@@ -11045,6 +11084,12 @@ os_readlink_impl(PyObject *module, path_t *path, int dir_fd)
1104511084 _Py_REPARSE_DATA_BUFFER *rdb = (_Py_REPARSE_DATA_BUFFER *)target_buffer;
1104611085 PyObject *result = NULL;
1104711086
11087+ if (path->is_fd) {
11088+ PyErr_SetString(PyExc_NotImplementedError,
11089+ "readlink cannot read file descriptors on this platform");
11090+ return NULL;
11091+ }
11092+
1104811093 /* First get a handle to the reparse point */
1104911094 Py_BEGIN_ALLOW_THREADS
1105011095 reparse_point_handle = CreateFileW(
@@ -18878,6 +18923,10 @@ PROBE(probe_openat, HAVE_OPENAT_RUNTIME)
1887818923PROBE(probe_readlinkat, HAVE_READLINKAT_RUNTIME)
1887918924#endif
1888018925
18926+ #ifdef _Py_HAVE_FREADLINK
18927+ PROBE(probe_freadlink, HAVE_FREADLINK_RUNTIME)
18928+ #endif
18929+
1888118930#ifdef HAVE_SYMLINKAT
1888218931PROBE(probe_symlinkat, HAVE_SYMLINKAT_RUNTIME)
1888318932#endif
@@ -18945,6 +18994,10 @@ static const struct have_function {
1894518994 { "HAVE_FPATHCONF", NULL },
1894618995#endif
1894718996
18997+ #ifdef _Py_HAVE_FREADLINK
18998+ { "HAVE_FREADLINK", probe_freadlink },
18999+ #endif
19000+
1894819001#ifdef HAVE_FSTATAT
1894919002 { "HAVE_FSTATAT", probe_fstatat },
1895019003#endif
0 commit comments