self.assertNotHasAttr(os, "pwritev")
self.assertNotHasAttr(os, "preadv")
+ def test_pipe2(self):
+ self._verify_available("HAVE_PIPE2")
+ if self.mac_ver >= (27, 0):
+ self.assertHasAttr(os, "pipe2")
+ else:
+ self.assertNotHasAttr(os, "pipe2")
+
+ def test_dup3(self):
+ self._verify_available("HAVE_DUP3")
+ r, w = os.pipe()
+ self.addCleanup(os.close, r)
+ self.addCleanup(os.close, w)
+ # Must not crash even when dup3 unavailable at runtime.
+ # os.dup2 returns fd2 (here w); do not double-close.
+ os.dup2(r, w, inheritable=False)
+
def test_stat(self):
self._verify_available("HAVE_FSTATAT")
if self.mac_ver >= (10, 10):
# define HAVE_MKFIFOAT_RUNTIME __builtin_available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
# define HAVE_MKNODAT_RUNTIME __builtin_available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
# define HAVE_PTSNAME_R_RUNTIME __builtin_available(macOS 10.13.4, iOS 11.3, tvOS 11.3, watchOS 4.3, *)
+# define HAVE_DUP3_RUNTIME __builtin_available(macOS 27.0, *)
+# define HAVE_PIPE2_RUNTIME __builtin_available(macOS 27.0, *)
# define HAVE_POSIX_SPAWN_SETSID_RUNTIME __builtin_available(macOS 10.15, *)
# define HAVE_PTSNAME_R_RUNTIME (ptsname_r != NULL)
# endif
+# ifdef HAVE_DUP3
+# define HAVE_DUP3_RUNTIME (dup3 != NULL)
+# endif
+
+# ifdef HAVE_PIPE2
+# define HAVE_PIPE2_RUNTIME (pipe2 != NULL)
+# endif
+
#endif
#ifdef HAVE_FUTIMESAT
# define HAVE_MKFIFOAT_RUNTIME 1
# define HAVE_MKNODAT_RUNTIME 1
# define HAVE_PTSNAME_R_RUNTIME 1
+# define HAVE_DUP3_RUNTIME 1
+# define HAVE_PIPE2_RUNTIME 1
#endif
/*[clinic end generated code: output=bc059d34a73404d1 input=c3cddda8922b038d]*/
{
int res = 0;
-#if defined(HAVE_DUP3) && \
- !(defined(HAVE_FCNTL_H) && defined(F_DUP2FD_CLOEXEC))
- /* dup3() is available on Linux 2.6.27+ and glibc 2.9 */
- static int dup3_works = -1;
-#endif
+
+ /* dup3() is available on Linux 2.6.27+ and glibc 2.9 and macOS 27.0;
+ * it needs runtime detection for the case of running on older kernels.
+ * Values: -1: unknown; 0: doesn't work; 1: works
+ * For thread safety, use a process-global with one read & one store,
+ * both relaxed. (It's fine if two threads race and do the detection
+ * simultaneously; they should get the same result.)
+ */
+ static int dup3_works_atomic = -1;
+ (void) dup3_works_atomic; // unused on some platforms
/* dup2() can fail with EINTR if the target FD is already open, because it
* then has to be closed. See os_close_impl() for why we don't handle EINTR
#else
#ifdef HAVE_DUP3
+ int dup3_works = FT_ATOMIC_LOAD_INT_RELAXED(dup3_works_atomic);
if (!inheritable && dup3_works != 0) {
- Py_BEGIN_ALLOW_THREADS
- res = dup3(fd, fd2, O_CLOEXEC);
- Py_END_ALLOW_THREADS
- if (res < 0) {
- if (dup3_works == -1)
- dup3_works = (errno != ENOSYS);
- if (dup3_works) {
- posix_error();
- return -1;
+ if (HAVE_DUP3_RUNTIME) {
+ Py_BEGIN_ALLOW_THREADS
+ res = dup3(fd, fd2, O_CLOEXEC);
+ Py_END_ALLOW_THREADS
+ if (res < 0) {
+ if (dup3_works == -1) {
+ dup3_works = (errno != ENOSYS);
+ FT_ATOMIC_STORE_INT_RELAXED(dup3_works_atomic, dup3_works);
+ }
+ if (dup3_works) {
+ posix_error();
+ return -1;
+ }
}
}
+ else {
+ dup3_works = 0;
+ FT_ATOMIC_STORE_INT_RELAXED(dup3_works_atomic, dup3_works);
+ }
}
if (inheritable || dup3_works == 0)
SECURITY_ATTRIBUTES attr;
BOOL ok;
#else
- int res;
+ int res = -1;
+
+ /* pipe2() is available on some newer linux/glibc & macOS;
+ * use the same runtime detection as for dup3 above.
+ */
+ static int pipe2_works_atomic = -1;
+ (void) pipe2_works_atomic; // unused on some platforms
#endif
#ifdef MS_WINDOWS
#else
#ifdef HAVE_PIPE2
- Py_BEGIN_ALLOW_THREADS
- res = pipe2(fds, O_CLOEXEC);
- Py_END_ALLOW_THREADS
+ int pipe2_works = FT_ATOMIC_LOAD_INT_RELAXED(pipe2_works_atomic);
+ if (pipe2_works != 0) {
+ if (HAVE_PIPE2_RUNTIME) {
+ Py_BEGIN_ALLOW_THREADS
+ res = pipe2(fds, O_CLOEXEC);
+ Py_END_ALLOW_THREADS
+ if (pipe2_works == -1) {
+ if (res != 0 && errno == ENOSYS) {
+ pipe2_works = 0;
+ }
+ else {
+ // pipe2 is present but this call failed
+ pipe2_works = 1;
+ }
+ FT_ATOMIC_STORE_INT_RELAXED(pipe2_works_atomic, pipe2_works);
+ }
+ }
+ else {
+ pipe2_works = 0;
+ FT_ATOMIC_STORE_INT_RELAXED(pipe2_works_atomic, pipe2_works);
+ }
+ }
- if (res != 0 && errno == ENOSYS)
+ if (pipe2_works == 0)
{
#endif
Py_BEGIN_ALLOW_THREADS
}
#endif
- if (res != 0)
+ if (res != 0) {
return PyErr_SetFromErrno(PyExc_OSError);
+ }
#endif /* !MS_WINDOWS */
return Py_BuildValue("(ii)", fds[0], fds[1]);
}
int fds[2];
int res;
- res = pipe2(fds, flags);
- if (res != 0)
+ if (HAVE_PIPE2_RUNTIME) {
+ res = pipe2(fds, flags);
+ }
+ else {
+ res = -1;
+ errno = ENOSYS;
+ }
+ if (res != 0) {
return posix_error();
+ }
+
return Py_BuildValue("(ii)", fds[0], fds[1]);
}
#endif /* HAVE_PIPE2 */
}
#endif
+#if HAVE_PIPE2
+ if (HAVE_PIPE2_RUNTIME) {
+ // Do nothing. (`__builtin_available` doesn't allow `!`; see
+ // "using negations" in a comment above.)
+ }
+ else {
+ PyObject* dct = PyModule_GetDict(m);
+ if (dct == NULL) {
+ return -1;
+ }
+ if (PyDict_PopString(dct, "pipe2", NULL) < 0) {
+ return -1;
+ }
+ }
+#endif
+
/* Initialize environ dictionary */
if (PyModule_Add(m, "environ", convertenviron()) != 0) {
return -1;