Skip to content

Commit 03077d5

Browse files
encukouVamsi-klu
andcommitted
Add runtime guards for dup3 & pipe2
Based on GH-154718 Co-Authored-By: Vamsi-klu <nrvamsi13@gmail.com>
1 parent ef0affb commit 03077d5

4 files changed

Lines changed: 110 additions & 22 deletions

File tree

Doc/library/os.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1448,7 +1448,7 @@ or `the MSDN <https://msdn.microsoft.com/en-us/library/z0kc8e3z.aspx>`_ on Windo
14481448
Return a pair of file descriptors ``(r, w)`` usable for reading and writing,
14491449
respectively.
14501450

1451-
.. availability:: Unix, not WASI, not macOS, not iOS.
1451+
.. availability:: Unix, macOS >= 27.0, not WASI, not iOS.
14521452

14531453
.. versionadded:: 3.3
14541454

Lib/test/test_os/test_posix.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2388,6 +2388,22 @@ def test_pwritev(self):
23882388
self.assertNotHasAttr(os, "pwritev")
23892389
self.assertNotHasAttr(os, "preadv")
23902390

2391+
def test_pipe2(self):
2392+
self._verify_available("HAVE_PIPE2")
2393+
if self.mac_ver >= (27, 0):
2394+
self.assertHasAttr(os, "pipe2")
2395+
else:
2396+
self.assertNotHasAttr(os, "pipe2")
2397+
2398+
def test_dup3(self):
2399+
self._verify_available("HAVE_DUP3")
2400+
r, w = os.pipe()
2401+
self.addCleanup(os.close, r)
2402+
self.addCleanup(os.close, w)
2403+
# Must not crash even when dup3 unavailable at runtime.
2404+
# os.dup2 returns fd2 (here w); do not double-close.
2405+
os.dup2(r, w, inheritable=False)
2406+
23912407
def test_stat(self):
23922408
self._verify_available("HAVE_FSTATAT")
23932409
if self.mac_ver >= (10, 10):
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
On macOS, add run-time checks around the syscalls :manpage:`pipe2 (2)` and
2+
:manpage:`dup3 (2)`, in addition to the existing build-time checks. This
3+
means that Python built on macOS 27 (where these calls are available) can
4+
run on macOS 26 (where they aren't).

Modules/posixmodule.c

Lines changed: 89 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,8 @@ static const unsigned int _Py_STATX_KNOWN = (STATX_BASIC_STATS | STATX_BTIME
504504
# define HAVE_MKFIFOAT_RUNTIME __builtin_available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
505505
# define HAVE_MKNODAT_RUNTIME __builtin_available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
506506
# define HAVE_PTSNAME_R_RUNTIME __builtin_available(macOS 10.13.4, iOS 11.3, tvOS 11.3, watchOS 4.3, *)
507+
# define HAVE_DUP3_RUNTIME __builtin_available(macOS 27.0, *)
508+
# define HAVE_PIPE2_RUNTIME __builtin_available(macOS 27.0, *)
507509

508510
# define HAVE_POSIX_SPAWN_SETSID_RUNTIME __builtin_available(macOS 10.15, *)
509511

@@ -589,6 +591,14 @@ static const unsigned int _Py_STATX_KNOWN = (STATX_BASIC_STATS | STATX_BTIME
589591
# define HAVE_PTSNAME_R_RUNTIME (ptsname_r != NULL)
590592
# endif
591593

594+
# ifdef HAVE_DUP3_RUNTIME
595+
# define HAVE_DUP3_RUNTIME (dup3 != NULL)
596+
# endif
597+
598+
# ifdef HAVE_PIPE2_RUNTIME
599+
# define HAVE_PIPE2_RUNTIME (pipe2 != NULL)
600+
# endif
601+
592602
#endif
593603

594604
#ifdef HAVE_FUTIMESAT
@@ -619,6 +629,8 @@ static const unsigned int _Py_STATX_KNOWN = (STATX_BASIC_STATS | STATX_BTIME
619629
# define HAVE_MKFIFOAT_RUNTIME 1
620630
# define HAVE_MKNODAT_RUNTIME 1
621631
# define HAVE_PTSNAME_R_RUNTIME 1
632+
# define HAVE_DUP3_RUNTIME 1
633+
# define HAVE_PIPE2_RUNTIME 1
622634
#endif
623635

624636

@@ -11866,11 +11878,13 @@ os_dup2_impl(PyObject *module, int fd, int fd2, int inheritable)
1186611878
/*[clinic end generated code: output=bc059d34a73404d1 input=c3cddda8922b038d]*/
1186711879
{
1186811880
int res = 0;
11869-
#if defined(HAVE_DUP3) && \
11870-
!(defined(HAVE_FCNTL_H) && defined(F_DUP2FD_CLOEXEC))
11871-
/* dup3() is available on Linux 2.6.27+ and glibc 2.9 */
11881+
11882+
/* dup3() is available on Linux 2.6.27+ and glibc 2.9 and macOS 27.0;
11883+
* it needs runtime detection for the case of running on older kernels.
11884+
* Values: -1: unknown; 0: doesn't work; 1: works
11885+
*/
1187211886
static int dup3_works = -1;
11873-
#endif
11887+
(void) dup3_works; // unused on some platforms
1187411888

1187511889
/* dup2() can fail with EINTR if the target FD is already open, because it
1187611890
* then has to be closed. See os_close_impl() for why we don't handle EINTR
@@ -11910,17 +11924,23 @@ os_dup2_impl(PyObject *module, int fd, int fd2, int inheritable)
1191011924

1191111925
#ifdef HAVE_DUP3
1191211926
if (!inheritable && dup3_works != 0) {
11913-
Py_BEGIN_ALLOW_THREADS
11914-
res = dup3(fd, fd2, O_CLOEXEC);
11915-
Py_END_ALLOW_THREADS
11916-
if (res < 0) {
11917-
if (dup3_works == -1)
11918-
dup3_works = (errno != ENOSYS);
11919-
if (dup3_works) {
11920-
posix_error();
11921-
return -1;
11927+
if (HAVE_DUP3_RUNTIME) {
11928+
Py_BEGIN_ALLOW_THREADS
11929+
res = dup3(fd, fd2, O_CLOEXEC);
11930+
Py_END_ALLOW_THREADS
11931+
if (res < 0) {
11932+
if (dup3_works == -1) {
11933+
dup3_works = (errno != ENOSYS);
11934+
}
11935+
if (dup3_works) {
11936+
posix_error();
11937+
return -1;
11938+
}
1192211939
}
1192311940
}
11941+
else {
11942+
dup3_works = 0;
11943+
}
1192411944
}
1192511945

1192611946
if (inheritable || dup3_works == 0)
@@ -12761,7 +12781,14 @@ os_pipe_impl(PyObject *module)
1276112781
SECURITY_ATTRIBUTES attr;
1276212782
BOOL ok;
1276312783
#else
12764-
int res;
12784+
int res = -1;
12785+
12786+
/* pipe2() is available on some newer linux/glibc & macOS;
12787+
* it needs runtime detection for the case of running on older kernels.
12788+
* Values: -1: unknown; 0: doesn't work; 1: works
12789+
*/
12790+
static int pipe2_works = -1;
12791+
(void) pipe2_works; // unused on some platforms
1276512792
#endif
1276612793

1276712794
#ifdef MS_WINDOWS
@@ -12787,11 +12814,27 @@ os_pipe_impl(PyObject *module)
1278712814
#else
1278812815

1278912816
#ifdef HAVE_PIPE2
12790-
Py_BEGIN_ALLOW_THREADS
12791-
res = pipe2(fds, O_CLOEXEC);
12792-
Py_END_ALLOW_THREADS
12817+
if (pipe2_works != 0) {
12818+
if (HAVE_PIPE2_RUNTIME) {
12819+
Py_BEGIN_ALLOW_THREADS
12820+
res = pipe2(fds, O_CLOEXEC);
12821+
Py_END_ALLOW_THREADS
12822+
if (pipe2_works == -1) {
12823+
if (res != 0 && errno == ENOSYS) {
12824+
pipe2_works = 0;
12825+
}
12826+
else {
12827+
// pipe2 is present but this call failed
12828+
pipe2_works = 1;
12829+
}
12830+
}
12831+
}
12832+
else {
12833+
pipe2_works = 0;
12834+
}
12835+
}
1279312836

12794-
if (res != 0 && errno == ENOSYS)
12837+
if (pipe2_works == 0)
1279512838
{
1279612839
#endif
1279712840
Py_BEGIN_ALLOW_THREADS
@@ -12814,8 +12857,9 @@ os_pipe_impl(PyObject *module)
1281412857
}
1281512858
#endif
1281612859

12817-
if (res != 0)
12860+
if (res != 0) {
1281812861
return PyErr_SetFromErrno(PyExc_OSError);
12862+
}
1281912863
#endif /* !MS_WINDOWS */
1282012864
return Py_BuildValue("(ii)", fds[0], fds[1]);
1282112865
}
@@ -12845,9 +12889,17 @@ os_pipe2_impl(PyObject *module, int flags)
1284512889
int fds[2];
1284612890
int res;
1284712891

12848-
res = pipe2(fds, flags);
12849-
if (res != 0)
12892+
if (HAVE_PIPE2_RUNTIME) {
12893+
res = pipe2(fds, flags);
12894+
}
12895+
else {
12896+
res = -1;
12897+
errno = ENOSYS;
12898+
}
12899+
if (res != 0) {
1285012900
return posix_error();
12901+
}
12902+
1285112903
return Py_BuildValue("(ii)", fds[0], fds[1]);
1285212904
}
1285312905
#endif /* HAVE_PIPE2 */
@@ -18844,6 +18896,22 @@ posixmodule_exec(PyObject *m)
1884418896
}
1884518897
#endif
1884618898

18899+
#if HAVE_PIPE2
18900+
if (HAVE_PIPE2_RUNTIME) {
18901+
// Do nothing. (`__builtin_available` doesn't allow `!`; see
18902+
// "using negations" in a comment above.)
18903+
}
18904+
else {
18905+
PyObject* dct = PyModule_GetDict(m);
18906+
if (dct == NULL) {
18907+
return -1;
18908+
}
18909+
if (PyDict_PopString(dct, "pipe2", NULL) < 0) {
18910+
return -1;
18911+
}
18912+
}
18913+
#endif
18914+
1884718915
/* Initialize environ dictionary */
1884818916
if (PyModule_Add(m, "environ", convertenviron()) != 0) {
1884918917
return -1;

0 commit comments

Comments
 (0)