From 6bfde8d2a239a9e8549de1ae29acecdefdfb8086 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 15 Sep 2026 20:09:14 +0900 Subject: [PATCH 1/3] [Bug #22311] Check the availability of APIs added in macOS 27 To account for cases where binaries for older OS versions are built using the new SDK, check at runtime whether the API functions are available actually. --- io.c | 108 +++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 80 insertions(+), 28 deletions(-) diff --git a/io.c b/io.c index 83ecb651bef351..e660eca8d41d8c 100644 --- a/io.c +++ b/io.c @@ -116,6 +116,10 @@ #endif +#if defined __APPLE__ +# include +#endif + #include "ruby/internal/stdbool.h" #include "ccan/list/list.h" #include "dln.h" @@ -245,6 +249,39 @@ struct argf { int8_t init_p, next_p, binmode; }; + +#if defined(__APPLE__) && \ + (!defined(MAC_OS_VERSION_27_0) || (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_VERSION_27_0)) + +# if __has_attribute(availability) && __has_warning("-Wunguarded-availability-new") + +RBIMPL_WARNING_PUSH() +RBIMPL_WARNING_IGNORED(-Wunguarded-availability-new) + +# ifdef HAVE_DUP3 +static inline int (*rb_dup3(void))(int, int, int) {return &dup3;} +# define dup3 rb_dup3() +# endif + +# ifdef HAVE_PIPE2 +static inline int (*rb_pipe2(void))(int [2], int) {return &pipe2;} +# define pipe2 rb_pipe2() +# endif + +RBIMPL_WARNING_POP() + +# else /* __API_AVAILABLE macro does nothing on gcc */ + +# ifdef HAVE_DUP3 +__attribute__((weak)) int dup3(int, int, int); +# endif +# ifdef HAVE_PIPE2 +__attribute__((weak)) int pipe2(int [2], int); +# endif + +# endif +#endif /* __APPLE__ && < MAC_OS_X_VERSION_27_0 */ + static rb_atomic_t max_file_descriptor = NOFILE; void rb_update_max_fd(int fd) @@ -384,23 +421,28 @@ rb_cloexec_dup2(int oldfd, int newfd) } else { #if defined(HAVE_DUP3) && defined(O_CLOEXEC) - static int try_dup3 = 1; - if (2 < newfd && try_dup3) { +# if defined(__APPLE__) +# define try_dup3 (dup3 != NULL) +# define abandon_dup3() true +# else + static bool try_dup3 = true; +# define abandon_dup3() (errno != ENOSYS || !!(try_dup3 = false)) +# endif + if (newfd <= 2) { + /* pass stdin, stdout and stderr to children */ + } + else if (try_dup3) { ret = dup3(oldfd, newfd, O_CLOEXEC); + /* dup3 is available since: + * - Linux 2.6.27, glibc 2.9 + * - macOS 27.0 + */ if (ret != -1) return ret; - /* dup3 is available since Linux 2.6.27, glibc 2.9. */ - if (errno == ENOSYS) { - try_dup3 = 0; - ret = dup2(oldfd, newfd); - } - } - else { - ret = dup2(oldfd, newfd); + if (abandon_dup3()) return ret; } -#else - ret = dup2(oldfd, newfd); #endif + ret = dup2(oldfd, newfd); if (ret < 0) return ret; } rb_maygvl_fd_fix_cloexec(ret); @@ -425,16 +467,25 @@ rb_fd_set_nonblock(int fd) return 0; } -int -rb_cloexec_pipe(int descriptors[2]) +static inline int +cloexec_pipe(int descriptors[2], int flags, bool force_cloexec) { + int result = -1; #ifdef HAVE_PIPE2 - int result = pipe2(descriptors, O_CLOEXEC | O_NONBLOCK); -#else - int result = pipe(descriptors); +# if defined(__APPLE__) +# define try_pipe2 (pipe2 != NULL) +# define abandon_pipe2() true +# else + static bool try_pipe2 = true; +# define abandon_pipe2() (errno != ENOSYS || !!(try_pipe2 = false)) +# endif + if (try_pipe2) { + result = pipe2(descriptors, O_CLOEXEC | flags); + if (result == 0) return result; + if (abandon_pipe2()) return result; + } #endif - - if (result < 0) + if (result < 0 && (result = pipe(descriptors)) < 0) return result; #ifdef __CYGWIN__ @@ -446,19 +497,26 @@ rb_cloexec_pipe(int descriptors[2]) } #endif -#ifndef HAVE_PIPE2 + if (!force_cloexec) return result; + + /* no pipe2 or fallenback to dup */ rb_maygvl_fd_fix_cloexec(descriptors[0]); rb_maygvl_fd_fix_cloexec(descriptors[1]); #ifndef _WIN32 rb_fd_set_nonblock(descriptors[0]); rb_fd_set_nonblock(descriptors[1]); -#endif #endif return result; } +int +rb_cloexec_pipe(int descriptors[2]) +{ + return cloexec_pipe(descriptors, O_NONBLOCK, true); +} + int rb_cloexec_fcntl_dupfd(int fd, int minfd) { @@ -8137,14 +8195,8 @@ ruby_popen_writer(char *const *argv, rb_pid_t *pid) int write_pair[2]; # endif -#ifdef HAVE_PIPE2 - int result = pipe2(write_pair, O_CLOEXEC); -#else - int result = pipe(write_pair); -#endif - *pid = -1; - if (result == 0) { + if (cloexec_pipe(write_pair, 0, false) == 0) { # ifdef HAVE_WORKING_FORK pw.argv = argv; int status; From 95bd25ed2758a1244c9e09941d074646e614dc69 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 15 Sep 2026 09:48:08 +0200 Subject: [PATCH 2/3] [ruby/resolv] Only require io/wait on Ruby versions earlier than 3.2 The io-wait library transitioned to core in 3.2 and is now an empty and deprecated gem. Only require it if running on Ruby versions earlier than 3.2. https://github.com/ruby/resolv/commit/2b5db02843 --- lib/resolv.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/resolv.rb b/lib/resolv.rb index aac5cdd2161af0..07a39f0cf409ea 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -2,7 +2,7 @@ require 'socket' require 'timeout' -require 'io/wait' +require 'io/wait' if RUBY_VERSION < '3.2' require 'securerandom' require 'rbconfig' From 16fd986706d84da6bb993b8c1b976a9cd9b6ddf0 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 15 Sep 2026 09:43:44 +0200 Subject: [PATCH 3/3] [ruby/net-protocol] Only require io/wait on Ruby versions earlier than 3.2 The io-wait library transitioned to core in 3.2 and is now an empty and deprecated gem. Only require it if running on Ruby versions earlier than 3.2. https://github.com/ruby/net-protocol/commit/1f84c9c60b --- lib/net/protocol.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/net/protocol.rb b/lib/net/protocol.rb index 7e42d226e23ce2..a62a269bb89bae 100644 --- a/lib/net/protocol.rb +++ b/lib/net/protocol.rb @@ -21,7 +21,7 @@ require 'socket' require 'timeout' -require 'io/wait' +require 'io/wait' if RUBY_VERSION < '3.2' module Net # :nodoc: