| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
Sorry, something went wrong.
| <dir_fd>`. | ||
|
|
||
| On MacOS and Linux, *path* can also be a file descriptor referring to a | ||
| symbolic link. In that case, *dir_fd* must be ``None``, and the return |
There was a problem hiding this comment.
Another sensible behaviour would be to ignore dir_fd completely, but I think raising an error here is the better alternative.
Sorry, something went wrong.
|
|
||
| On MacOS and Linux, *path* can also be a file descriptor referring to a | ||
| symbolic link. In that case, *dir_fd* must be ``None``, and the return | ||
| value will be a ``bytes`` object. |
There was a problem hiding this comment.
This just so happened - maybe a str is more reasonable?
Sorry, something went wrong.
There was a problem hiding this comment.
Yes, readlins returns str (with surogatepass encoding, so you can get the bytes out losslessly).
Sorry, something went wrong.
There was a problem hiding this comment.
Updated: The implementation returns a "string" (via PyUnicode_DecodeFSDefaultAndSize), the tests check for equality with a string ("symlink" rather than b"symlink"), and the docs are updated, too.
Sorry, something went wrong.
Documentation build overview53 files changed · + 1 added · ± 52 modified + Added ± Modified |
Sorry, something went wrong.
|
One open question is whether os.readlink() should be added to supports_fd. I don't have an opinion on that, happy to take others' lead here. |
Sorry, something went wrong.
…ink(). This feature is only supported on Linux, Android and MacOS, other OSs should raise a NotImplementedError().
|
|
||
| On MacOS and Linux, *path* can also be a file descriptor referring to a | ||
| symbolic link. In that case, *dir_fd* must be ``None``, and the return | ||
| value will be a ``bytes`` object. |
There was a problem hiding this comment.
Yes, readlins returns str (with surogatepass encoding, so you can get the bytes out losslessly).
Sorry, something went wrong.
| } | ||
|
|
||
| if (path->is_fd) { | ||
| #if defined(__APPLE__) && defined(_Py_HAVE_FREADLINK) |
There was a problem hiding this comment.
I think we only want _Py_HAVE_FREADLINK here -- if any other platform has it, it's the function to call.
Sorry, something went wrong.
There was a problem hiding this comment.
Removed the defined(__APPLE__) check.
Sorry, something went wrong.
| <dir_fd>`. | ||
|
|
||
| On MacOS and Linux, *path* can also be a file descriptor referring to a | ||
| symbolic link. In that case, *dir_fd* must be ``None``, and the return |
There was a problem hiding this comment.
It would be nice to document how to get such a file descriptor (O_SYMLINK and O_PATH|O_NOFOLLOW).
Sorry, something went wrong.
There was a problem hiding this comment.
Added this to the docs.
Sorry, something went wrong.
| int readlinkat_unavailable = 0; | ||
| #endif | ||
| #ifdef _Py_HAVE_FREADLINK | ||
| int freadlink_unavailable = 0; |
There was a problem hiding this comment.
AFAICS, we don't need the *_unavailable variables. The pattern is sometimes used because we can't raise exceptions inside Py_BEGIN_ALLOW_THREADS/Py_END_ALLOW_THREADS, but here, moving the Py_BEGIN_ALLOW_THREADS/Py_END_ALLOW_THREADS to tightly wrap each freadlink/readlinkat/readlink call would allow setting the exception & returning direclty in the else blocks.
Sorry, something went wrong.
There was a problem hiding this comment.
Done. I dropped the freadlink_unavailable variable and the error is now raised directly in the else branch.
I also "inlined" all the invocations of Py_BEGIN_ALLOW_THREADS and Py_END_ALLOW_THREADS, each syscall (freadlink, readlinkat in the path->is_fd case, readlinkat in the old dir_fd case, readlink) are now directly surrounded by those macros.
This is technically a bit of a refactor and slightly out-of-scope of the issue - but I hope it's okay to piggy-back this refactor onto this MR.
Sorry, something went wrong.
| // Linux: readlinkat(dir_fd, "", ...) reads the symbolic link | ||
| // pointed to by dir_fd | ||
| dir_fd = path->fd; | ||
| path->narrow = ""; |
There was a problem hiding this comment.
Putting the values in these variables is misleading.
Instead, could you add a separate block below with a call to readlinkat(path->fd, "", ...) & the error handling?
Sorry, something went wrong.
There was a problem hiding this comment.
Done. We now have a if (path->is_fd) { ... } else if (path->dir_fd != DEFAULT_DIR_FD) { ... } else { ... } structure.
The different implementations of this feature (freadlink on MacOS, readlinkat on Linux/Android, NotImplementedError on Android) are now all inside the if (path->is_fd) { ... } branch.
Sorry, something went wrong.
| if (path->is_fd) { | ||
| #if defined(__APPLE__) && defined(_Py_HAVE_FREADLINK) | ||
| /* nop, freadlink is called below */ | ||
| #elif defined(__linux__) && defined(HAVE_READLINKAT) |
There was a problem hiding this comment.
I think it would be useful to make this a configure variable, something like _Py_READLINKAT_SUPPORTS_EMPTY_PATH, only set on Linux.
Then, when someone ports CPython to another system that uses this (or to ancient Linux that doesn't), they can just change pyconfig.h or the configure script.
Alternately, use defined(HAVE_READLINKAT) && defined(O_PATH). O_PATH and empty pathname support were added in Linux 2.6.39; they look like it's essentially a single feature.
Sorry, something went wrong.
There was a problem hiding this comment.
I tried with defined(HAVE_READLINKAT) && defined(O_PATH), but that broke on the emscripten build. (Apparently both Macros are defined there even though readlink is not supported on that platform AFAIK.)
So now _Py_READLINKAT_SUPPORTS_EMPTY_PATH is set automatically on Linux and Android (that have O_PATH) above. Anyone who really wants it can still force that variable.
I didn't add anything about this to the autoconfig part - I hope that's okay. If you want it threaded through/into the configure.ac somehow, I can look into that.
Sorry, something went wrong.
| (Contributed by Md Arif in :gh:`152936`.) | ||
|
|
||
| * :func:`os.readlink` now accepts a file descriptor referring to a symlink on | ||
| Linux, Android and MacOS. |
There was a problem hiding this comment.
Could you add Android to the other docs?
Sorry, something went wrong.
There was a problem hiding this comment.
Done.
Sorry, something went wrong.
| _support_readlink_with_fd = hasattr(os, 'readlink') and ( | ||
| "HAVE_FREADLINK" in posix._have_functions # MacOS | ||
| or ( | ||
| os.readlink in os.supports_dir_fd | ||
| and sys.platform in ["linux", "android"] | ||
| ) | ||
| ) |
There was a problem hiding this comment.
One open question is whether os.readlink() should be added to supports_fd. I don't have an opinion on that, happy to take others' lead here.
I'd say add them. O_SYMLINK/O_PATH|O_NOFOLLOW are the way to get fds referring to a symlink on their respective platform, so this can be just os.readlink in os.supports_dir_fd.
(Yes, I changed my mind after going through the code.)
Sorry, something went wrong.
There was a problem hiding this comment.
Then, please also test that os.readlink in os.supports_dir_fd holds on MacOS, Linux & Android.
Sorry, something went wrong.
There was a problem hiding this comment.
Done. os.readlink is now added automatically to os.supports_fd. I also added a test that checks presence/absence on all platforms where both os.readlink and os.supports_fd are defined.
Sorry, something went wrong.
There was a problem hiding this comment.
_support_readlink_with_fd should now be equivalent to hasattr(os, 'readlink') and (os.readlink in os.supports_fd). Or more fancy, (getattr(os, 'readlink', None) in os.supports_fd)
The in expression can be used directly, as in the test above; no need for a _support_readlink_with_fd wariable.
Sorry, something went wrong.
There was a problem hiding this comment.
I'm updating this, but I'd prefer to keep the _support_readlink_with_fd variable (just for avoiding code/logic duplication).
As a side effect, I'm now removing the self.assertEqual(os.readlink in os.supports_fd, self._support_readlink_with_fd) test that you asked for, since this test would be tautological. (It currently sits behind a @unittest.skipUnless(hasattr(os, "readlink")) guard.)
Sorry, something went wrong.
There was a problem hiding this comment.
Okay, rather than removing the test completely I added something closer to what you originally asked for:
@unittest.skipUnless(sys.platform in ["linux", "android", "darwin", "ios"],
"feature not supported on this platform")
def test_readlink_is_marked_as_supports_fd(self):
self.assertIn(os.readlink, os.supports_fd)
Sorry, something went wrong.
…ddress feedback from MR. * os.readlink() now returns a `str` (rather than `bytes`) when called with a file descriptor. * os.readlink now added to `os.supports_fd` on the platforms where this feature is supported. * Small refactor inside `os.readlink`: drop *_unavailable variables, generate error messages inline. Py_BEGIN_ALLOW_THREADS/Py_END_ALLOW_THREADS around each syscall site. * Docs: Android is now mentioned as supported platform for this feature in all relevant places.
Emscripten apparrently defines O_PATH and readlinkat - even though it doesn't actually support them. This broke auto-detection of whether os.readlink supports FDs - which broke the tests.
|
Thank you for your review @encukou . I updated this PR based on your comments. Please re-review when you can spare some minutes. |
Sorry, something went wrong.
There was a problem hiding this comment.
macOS should be written "macOS", not "MacOS".
Sorry, something went wrong.
After changing return type of os.readlink when called with an fd from bytes to string, missed one place in the docs that needed updating.
* 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).
There was a problem hiding this comment.
Overall, the change LGTM. I just have another minor comment.
@encukou: I let you double check the change :-)
I was curious and replaced readlinkat(path->fd, "", buffer, MAXPATHLEN) with readlinkat(path->fd, "", buffer, MAXPATHLEN). With this change, the function fails with OSError: [Errno 14] Bad address: 6. So no, it's not possible to pass NULL: passing an empty string is the right argument.
Sorry, something went wrong.
| @unittest.skipUnless(hasattr(os, "supports_fd") and hasattr(os, "readlink"), | ||
| "feature not supported on this platform") |
There was a problem hiding this comment.
According to other tests, hasattr(os, "supports_fd") test is not needed, the attribute can always be used.
| @unittest.skipUnless(hasattr(os, "supports_fd") and hasattr(os, "readlink"), | |
| "feature not supported on this platform") | |
| @unittest.skipUnless(hasattr(os, "readlink"), "need os.readlink") |
Sorry, something went wrong.
There was a problem hiding this comment.
Sorry, I missed this comment. I just pushed an update: 6c8f5f1
Originally I wanted to be careful (because the supports_fd set is only defined within an if _exists("_have_functions") guard), but it appears that the _have_functions list is defined unconditionally (at least in the posixmodule.c).
Sorry, something went wrong.
I will, after the 3.15.0 release. Please don't merge yet. |
Sorry, something went wrong.
…s-in-os-readlink and resolved merge conflicts.
|
Resolved a merge conflict with commit cfd6cf7 on a "clinic end generated code" line: cfd6cf71547e#diff-f4ec448d9bc73d373452e97931de916ca83058ae93bce4a5853b8ac18c9d2cfa |
Sorry, something went wrong.
There was a problem hiding this comment.
Thank you! This is looking great, I have a few cleanups to suggest:
Sorry, something went wrong.
| _support_readlink_with_fd = hasattr(os, 'readlink') and ( | ||
| "HAVE_FREADLINK" in posix._have_functions # MacOS | ||
| or ( | ||
| os.readlink in os.supports_dir_fd | ||
| and sys.platform in ["linux", "android"] | ||
| ) | ||
| ) |
There was a problem hiding this comment.
_support_readlink_with_fd should now be equivalent to hasattr(os, 'readlink') and (os.readlink in os.supports_fd). Or more fancy, (getattr(os, 'readlink', None) in os.supports_fd)
The in expression can be used directly, as in the test above; no need for a _support_readlink_with_fd wariable.
Sorry, something went wrong.
…ink with a FD referring to a symlink. Also some code/test cleanup.
There was a problem hiding this comment.
Thank you, looks good to go now!
Sorry, something went wrong.
|
MSan: test_interrupt() of test_multiprocessing_forkserver.test_processes failed with a timeout. I suppose that re-run the job should be enough to work around the issue. ERROR: test_interrupt (test.test_multiprocessing_forkserver.test_processes.WithProcessesTestProcess.test_interrupt)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/runner/work/cpython/cpython/Lib/contextlib.py", line 116, in inner
return func(*args, **kwds)
File "/home/runner/work/cpython/cpython/Lib/test/_test_multiprocessing.py", line 651, in test_interrupt
exitcode = self._kill_process(multiprocessing.Process.interrupt)
File "/home/runner/work/cpython/cpython/Lib/contextlib.py", line 116, in inner
return func(*args, **kwds)
File "/home/runner/work/cpython/cpython/Lib/test/_test_multiprocessing.py", line 632, in _kill_process
self.assertEqual(join(), None)
~~~~^^
File "/home/runner/work/cpython/cpython/Lib/test/_test_multiprocessing.py", line 305, in __call__
return self.func(*args, **kwds)
~~~~~~~~~^^^^^^^^^^^^^^^
File "/home/runner/work/cpython/cpython/Lib/multiprocessing/process.py", line 156, in join
res = self._popen.wait(timeout)
File "/home/runner/work/cpython/cpython/Lib/multiprocessing/popen_fork.py", line 44, in wait
return self.poll(os.WNOHANG if timeout == 0.0 else 0)
~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/cpython/cpython/Lib/multiprocessing/popen_forkserver.py", line 65, in poll
if not wait([self.sentinel], timeout):
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/cpython/cpython/Lib/multiprocessing/connection.py", line 1197, in wait
ready = selector.select(timeout)
File "/home/runner/work/cpython/cpython/Lib/selectors.py", line 398, in select
fd_event_list = self._selector.poll(timeout)
File "/home/runner/work/cpython/cpython/Lib/test/_test_multiprocessing.py", line 628, in handler
raise RuntimeError('join took too long: %s' % p)
RuntimeError: join took too long: <Process name='Process-167' pid=20444 parent=18089 started daemon>
|
Sorry, something went wrong.
|
Thank you for your support and your patience @encukou and @vstinner . I'm glad this is merged now. If I can help with the post-merge issues (e.g. the timeout in the forkserver tests; though this looks very much unrelated) somehow, please let me know. |
Sorry, something went wrong.
|
The new test_readlink_is_marked_as_supports_fd() failed on macOS 12.7.6. posix.HAVE_FREADLINK checks for macOS 13.0 or newer, and so is false on macOS 12.7.6 if I understood correctly. x86-64 macOS 3.x: https://buildbot.python.org/#/builders/366/builds/12819 FAIL: test_readlink_is_marked_as_supports_fd (test.test_os.test_posix.TestPosixDirFd.test_readlink_is_marked_as_supports_fd)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/buildbot/buildarea/3.x.billenstein-macos/build/Lib/test/test_os/test_posix.py", line 1950, in test_readlink_is_marked_as_supports_fd
self.assertIn(os.readlink, os.supports_fd)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: <built-in function readlink> not found in {<built-in function scandir>, <built-in function statvfs>, <built-in function pathconf>, <built-in function chdir>, <built-in function stat>, <built-in function chown>, <built-in function truncate>, <built-in function utime>, <built-in function chmod>, <built-in function listdir>}
test.pythoninfo: platform.platform: macOS-12.7.6-x86_64-i386-64bit-Mach-O os.supports_dir_fd: ['access', 'chmod', 'chown', 'link', 'lstat', 'mkdir', 'open', 'readlink', 'rename', 'rmdir', 'stat', 'symlink', 'unlink', 'utime'] os.supports_fd: ['chdir', 'chmod', 'chown', 'listdir', 'pathconf', 'scandir', 'stat', 'statvfs', 'truncate', 'utime'] os.supports_follow_symlinks: ['access', 'chflags', 'chmod', 'chown', 'link', 'stat', 'utime'] configure: checking for readlink... yes checking for readlinkat... yes checking for freadlink... yes Lib/os.py contains: _add("HAVE_FREADLINK", "readlink")
Modules/posixmodule.c contains: # define HAVE_FREADLINK_RUNTIME __builtin_available(macOS 13.0, iOS 16.0, *)
#ifdef _Py_HAVE_FREADLINK
PROBE(probe_freadlink, HAVE_FREADLINK_RUNTIME)
#endif
#ifdef _Py_HAVE_FREADLINK
{ "HAVE_FREADLINK", probe_freadlink },
#endif
|
Sorry, something went wrong.
|
The test expects that os.readlink is always in os.supports_fd on macOS and iOS, but it depends on the macOS and iOS version: @unittest.skipUnless(sys.platform in ["linux", "android", "darwin", "ios"],
"feature not supported on this platform")
def test_readlink_is_marked_as_supports_fd(self):
self.assertIn(os.readlink, os.supports_fd) |
Sorry, something went wrong.
|
Python now fails to build on FreeBSD 16 because of this change. I wrote #159397 to fix FreeBSD. AMD64 FreeBSD16 3.x: https://buildbot.python.org/#/builders/1857/builds/934 --- Modules/posixmodule.o ---
./Modules/posixmodule.c:11030:13: error: use of undeclared identifier 'HAVE_FREADLINK_RUNTIME'
11030 | if (HAVE_FREADLINK_RUNTIME) {
| ^~~~~~~~~~~~~~~~~~~~~~
./Modules/posixmodule.c:18927:24: error: use of undeclared identifier 'HAVE_FREADLINK_RUNTIME'
18927 | PROBE(probe_freadlink, HAVE_FREADLINK_RUNTIME)
| ^~~~~~~~~~~~~~~~~~~~~~
2 errors generated.
configure: checking for readlink... yes checking for readlinkat... yes checking for freadlink... yes |
Sorry, something went wrong.
|
Well, shit. I'm on vacation as of today. I'll see if I can find some time to push a fix during that. |
Sorry, something went wrong.
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
See issue #157899 and https://discuss.python.org/t/support-file-descriptors-in-os-readlink/109101/6 for context.
CC @encukou