From e774a088c7d056dae59fd2b141fe0ba555bf9a43 Mon Sep 17 00:00:00 2001
From: Michal Suchanek <msuchanek@suse.de>
Date: Wed, 4 Mar 2020 23:34:09 +0100
Subject: [PATCH 10/12] select_with_poll: do not reaise exception on bad fd.

poll() returns an error for each bad descriptor rather than one global
error. FOrward the information to user.

Signed-off-by: Michal Suchanek <msuchanek@suse.de>
---
 include/ruby/internal/intern/select.h         |  2 +-
 .../ruby/internal/intern/select/largesize.h   |  2 +-
 include/ruby/internal/intern/select/win32.h   |  2 +-
 include/ruby/io.h                             |  2 +-
 internal/io.h                                 |  2 +-
 io.c                                          | 26 ++++++++++---------
 thread.c                                      | 20 +++++++++-----
 thread_pthread.c                              |  4 +--
 thread_win32.c                                |  3 ++-
 9 files changed, 36 insertions(+), 27 deletions(-)

diff --git a/include/ruby/internal/intern/select.h b/include/ruby/internal/intern/select.h
index 9d07b3503d76..5e68a100d9b4 100644
--- a/include/ruby/internal/intern/select.h
+++ b/include/ruby/internal/intern/select.h
@@ -45,7 +45,7 @@ RBIMPL_SYMBOL_EXPORT_BEGIN()
 
 struct timeval;
 
-int rb_thread_fd_select(int, rb_fdset_t *, rb_fdset_t *, rb_fdset_t *, rb_fdset_t *, struct timeval *);
+int rb_thread_fd_select(int, rb_fdset_t *, rb_fdset_t *, rb_fdset_t *, rb_fdset_t *, struct timeval *, _Bool);
 
 RBIMPL_SYMBOL_EXPORT_END()
 
diff --git a/include/ruby/internal/intern/select/largesize.h b/include/ruby/internal/intern/select/largesize.h
index 7d75c4718a0b..78fd64d41e8d 100644
--- a/include/ruby/internal/intern/select/largesize.h
+++ b/include/ruby/internal/intern/select/largesize.h
@@ -80,7 +80,7 @@ void rb_fd_clr(int, rb_fdset_t *);
 int rb_fd_isset(int, const rb_fdset_t *);
 void rb_fd_copy(rb_fdset_t *, const fd_set *, int);
 void rb_fd_dup(rb_fdset_t *dst, const rb_fdset_t *src);
-int rb_fd_select(int, rb_fdset_t *, rb_fdset_t *, rb_fdset_t *, rb_fdset_t *, struct timeval *);
+int rb_fd_select(int, rb_fdset_t *, rb_fdset_t *, rb_fdset_t *, rb_fdset_t *, struct timeval *, _Bool);
 RBIMPL_SYMBOL_EXPORT_END()
 
 RBIMPL_ATTR_NONNULL(())
diff --git a/include/ruby/internal/intern/select/win32.h b/include/ruby/internal/intern/select/win32.h
index e63bbfd62af4..93f143f2f490 100644
--- a/include/ruby/internal/intern/select/win32.h
+++ b/include/ruby/internal/intern/select/win32.h
@@ -91,7 +91,7 @@ rb_fd_dup(rb_fdset_t *dst, const rb_fdset_t *src)
 }
 
 static inline int
-rb_fd_select(int n, rb_fdset_t *rfds, rb_fdset_t *wfds, rb_fdset_t *efds, rb_fdset_t *errfds, struct timeval *timeout)
+rb_fd_select(int n, rb_fdset_t *rfds, rb_fdset_t *wfds, rb_fdset_t *efds, rb_fdset_t *errfds, struct timeval *timeout, _Bool select_iface)
 {
     return rb_w32_select(
         n,
diff --git a/include/ruby/io.h b/include/ruby/io.h
index 057475ba89a0..46507b7e9fd4 100644
--- a/include/ruby/io.h
+++ b/include/ruby/io.h
@@ -35,7 +35,7 @@
 #  define RB_WAITFD_IN  POLLIN
 #  define RB_WAITFD_PRI POLLPRI
 #  define RB_WAITFD_OUT POLLOUT
-#  define RB_WAITFD_ERR (POLLHUP|POLLERR)
+#  define RB_WAITFD_ERR (POLLHUP|POLLERR|POLLNVAL)
 #else
 #  define RB_WAITFD_IN  0x001
 #  define RB_WAITFD_PRI 0x002
diff --git a/internal/io.h b/internal/io.h
index c87aff3f6f07..9ceda018ac69 100644
--- a/internal/io.h
+++ b/internal/io.h
@@ -30,7 +30,7 @@
 #define POLLIN_SET (POLLRDNORM | POLLRDBAND | POLLIN)
 #define POLLOUT_SET (POLLWRBAND | POLLWRNORM | POLLOUT)
 #define POLLEX_SET (POLLPRI)
-#define POLLERR_SET (POLLHUP | POLLERR)
+#define POLLERR_SET (POLLHUP | POLLERR | POLLNVAL)
 #endif
 
 /* io.c */
diff --git a/io.c b/io.c
index dd4af1ee35fc..6c65958c9a4e 100644
--- a/io.c
+++ b/io.c
@@ -9152,7 +9152,7 @@ rb_f_backquote(VALUE obj, VALUE str)
 #endif
 
 static VALUE
-select_internal(VALUE read, VALUE write, VALUE except, VALUE error, struct timeval *tp, rb_fdset_t *fds)
+select_internal(VALUE read, VALUE write, VALUE except, VALUE error, struct timeval *tp, bool select_iface, rb_fdset_t *fds)
 {
     VALUE res, list;
     rb_fdset_t *rp, *wp, *ep, *erp;
@@ -9215,7 +9215,7 @@ select_internal(VALUE read, VALUE write, VALUE except, VALUE error, struct timev
 	ep = 0;
     }
 
-    if (!NIL_P(error)) {
+    if (!NIL_P(error) && !select_iface) {
 	Check_Type(error, T_ARRAY);
 	for (i=0; i<RARRAY_LEN(error); i++) {
             VALUE io = rb_io_get_io(RARRAY_AREF(error, i));
@@ -9237,7 +9237,7 @@ select_internal(VALUE read, VALUE write, VALUE except, VALUE error, struct timev
 
     max++;
 
-    n = rb_thread_fd_select(max, rp, wp, ep, erp, tp);
+    n = rb_thread_fd_select(max, rp, wp, ep, erp, tp, select_iface);
     if (n < 0) {
 	rb_sys_fail(0);
     }
@@ -9247,7 +9247,8 @@ select_internal(VALUE read, VALUE write, VALUE except, VALUE error, struct timev
     rb_ary_push(res, rp?rb_ary_new():rb_ary_new2(0));
     rb_ary_push(res, wp?rb_ary_new():rb_ary_new2(0));
     rb_ary_push(res, ep?rb_ary_new():rb_ary_new2(0));
-    rb_ary_push(res, erp?rb_ary_new():rb_ary_new2(0));
+    if (!select_iface)
+	rb_ary_push(res, erp?rb_ary_new():rb_ary_new2(0));
 
     if (rp) {
 	list = RARRAY_AREF(res, 0);
@@ -9319,6 +9320,7 @@ select_internal(VALUE read, VALUE write, VALUE except, VALUE error, struct timev
 struct select_args {
     VALUE read, write, except, error;
     struct timeval *timeout;
+    bool select_iface;
     rb_fdset_t fdsets[5];
 };
 
@@ -9327,7 +9329,7 @@ select_call(VALUE arg)
 {
     struct select_args *p = (struct select_args *)arg;
 
-    return select_internal(p->read, p->write, p->except, p->error, p->timeout, p->fdsets);
+    return select_internal(p->read, p->write, p->except, p->error, p->timeout, p->select_iface, p->fdsets);
 }
 
 static VALUE
@@ -9535,8 +9537,9 @@ rb_io_advise(int argc, VALUE *argv, VALUE io)
  *
  *  On systems that support poll(2) system call IO.select_with_poll is provided
  *  which takes and returns extra array of descriptors. Descriptors in this
- *  extra array and all other arrays are checked for error conditions -
- *  typically the other side closing the pipe or socket.
+ *  extra array are checked for error conditions - typically the other side
+ *  closing the pipe or socket. Descriptors returned in fourth array instead of
+ *  raising Errno::EBADF.
  *
  *  IO.select peeks the buffer of IO objects for testing readability.
  *  If the IO buffer is not empty, IO.select immediately notifies
@@ -9675,21 +9678,20 @@ rb_f_select_with_poll(int argc, VALUE *argv, VALUE obj)
     struct select_args args;
 
     rb_scan_args(argc, argv, "14", &args.read, &args.write, &args.except, &args.error, &timeout);
+    args.select_iface = false;
     return do_rb_f_select(&args, timeout);
 }
 #endif
 static VALUE
 rb_f_select(int argc, VALUE *argv, VALUE obj)
 {
-    VALUE timeout, rv;
+    VALUE timeout;
     struct select_args args;
 
     rb_scan_args(argc, argv, "13", &args.read, &args.write, &args.except, &timeout);
     args.error = Qnil;
-    rv = do_rb_f_select(&args, timeout);
-    if (RB_TYPE_P(rv, T_ARRAY))
-        rb_ary_pop(rv);
-    return rv;
+    args.select_iface = true;
+    return do_rb_f_select(&args, timeout);
 }
 
 static VALUE
diff --git a/thread.c b/thread.c
index c5177825f5b5..0a52a7e16b47 100644
--- a/thread.c
+++ b/thread.c
@@ -3943,7 +3943,7 @@ pollfdset_extract(struct rb_pollfdset *pollfdset, rb_fdset_t *fds, int flags)
 
     rb_fd_zero(fds);
     for (i = 0; i < pollfdset->n_fds; i++)
-        if (pollfdset->fds[i].revents & (flags | POLLERR_SET))
+        if (pollfdset->fds[i].revents & flags)
             rb_fd_set(pollfdset->fds[i].fd, fds);
 }
 
@@ -3960,11 +3960,12 @@ pollfdset_badfd(struct rb_pollfdset *pollfdset)
 }
 
 int
-rb_fd_select(int n, rb_fdset_t *readfds, rb_fdset_t *writefds, rb_fdset_t *exceptfds, rb_fdset_t *errorfds, struct timeval *timeout)
+rb_fd_select(int n, rb_fdset_t *readfds, rb_fdset_t *writefds, rb_fdset_t *exceptfds, rb_fdset_t *errorfds,
+             struct timeval *timeout, bool select_iface)
 {
     struct rb_pollfdset pollfdset;
     int timeout_ms = timeout ? timeout->tv_sec * 1000 + timeout->tv_usec / 1000 : -1;
-    bool badfd;
+    bool badfd = false;
     int ret;
 
     pollfdset_init(&pollfdset);
@@ -3973,14 +3974,16 @@ rb_fd_select(int n, rb_fdset_t *readfds, rb_fdset_t *writefds, rb_fdset_t *excep
     if (exceptfds) pollfdset_add(&pollfdset, exceptfds, POLLEX_SET);
     if (errorfds) pollfdset_add(&pollfdset, errorfds, POLLERR_SET);
     ret = poll(pollfdset.fds, pollfdset.n_fds, timeout_ms);
-    badfd = pollfdset_badfd(&pollfdset);
+    if (select_iface)
+        badfd = pollfdset_badfd(&pollfdset);
     if (badfd) {
         if (readfds) rb_fd_zero(readfds);
         if (writefds) rb_fd_zero(writefds);
         if (exceptfds) rb_fd_zero(exceptfds);
         if (errorfds) rb_fd_zero(errorfds);
     } else {
-        if (readfds) pollfdset_extract(&pollfdset, readfds, POLLIN_SET);
+        if (readfds) pollfdset_extract(&pollfdset, readfds, POLLIN_SET |
+                (select_iface ? POLLERR_SET : 0));
         if (writefds) pollfdset_extract(&pollfdset, writefds, POLLOUT_SET);
         if (exceptfds) pollfdset_extract(&pollfdset, exceptfds, POLLEX_SET);
         if (errorfds) pollfdset_extract(&pollfdset, errorfds, POLLERR_SET);
@@ -4131,6 +4134,7 @@ struct select_set {
     rb_fdset_t orig_eset;
     rb_fdset_t orig_errset;
     struct timeval *timeout;
+    _Bool select_iface;
 };
 
 static VALUE
@@ -4196,7 +4200,8 @@ do_select(VALUE p)
             if (!RUBY_VM_INTERRUPTED(set->th->ec)) {
                 result = native_fd_select(set->max, set->rset, set->wset,
                                           set->eset, set->errset,
-                                          rb_hrtime2timeval(&tv, sto), set->th);
+                                          rb_hrtime2timeval(&tv, sto),
+                                          set->select_iface, set->th);
                 if (result < 0) lerrno = errno;
             }
 	}, set->sigwait_fd >= 0 ? ubf_sigwait : ubf_select, set->th, TRUE);
@@ -4267,7 +4272,7 @@ init_set_fd(int fd, rb_fdset_t *fds)
 
 int
 rb_thread_fd_select(int max, rb_fdset_t * read, rb_fdset_t * write, rb_fdset_t * except,
-		    rb_fdset_t * error, struct timeval *timeout)
+                    rb_fdset_t * error, struct timeval *timeout, _Bool select_iface)
 {
     struct select_set set;
 
@@ -4279,6 +4284,7 @@ rb_thread_fd_select(int max, rb_fdset_t * read, rb_fdset_t * write, rb_fdset_t *
     set.eset = except;
     set.errset = error;
     set.timeout = timeout;
+    set.select_iface = select_iface;
 
     if (!set.rset && !set.wset && !set.eset && !set.errset) {
         if (!timeout) {
diff --git a/thread_pthread.c b/thread_pthread.c
index 66f4f480cd56..2f0380b3f372 100644
--- a/thread_pthread.c
+++ b/thread_pthread.c
@@ -1156,9 +1156,9 @@ native_thread_apply_priority(rb_thread_t *th)
 #endif /* USE_NATIVE_THREAD_PRIORITY */
 
 static int
-native_fd_select(int n, rb_fdset_t *readfds, rb_fdset_t *writefds, rb_fdset_t *exceptfds, rb_fdset_t *errorfds, struct timeval *timeout, rb_thread_t *th)
+native_fd_select(int n, rb_fdset_t *readfds, rb_fdset_t *writefds, rb_fdset_t *exceptfds, rb_fdset_t *errorfds, struct timeval *timeout, _Bool select_iface, rb_thread_t *th)
 {
-    return rb_fd_select(n, readfds, writefds, exceptfds, errorfds, timeout);
+    return rb_fd_select(n, readfds, writefds, exceptfds, errorfds, timeout, select_iface);
 }
 
 static void
diff --git a/thread_win32.c b/thread_win32.c
index d8eb2b1ee6c9..6ca95edf2278 100644
--- a/thread_win32.c
+++ b/thread_win32.c
@@ -640,7 +640,8 @@ native_thread_apply_priority(rb_thread_t *th)
 int rb_w32_select_with_thread(int, fd_set *, fd_set *, fd_set *, struct timeval *, void *);	/* @internal */
 
 static int
-native_fd_select(int n, rb_fdset_t *readfds, rb_fdset_t *writefds, rb_fdset_t *exceptfds, struct timeval *timeout, rb_thread_t *th)
+native_fd_select(int n, rb_fdset_t *readfds, rb_fdset_t *writefds, rb_fdset_t *exceptfds,
+                 struct timeval *timeout, _Bool select_iface, rb_thread_t *th)
 {
     fd_set *r = NULL, *w = NULL, *e = NULL;
     if (readfds) {
-- 
2.26.2

