tevent: revalidate fde->flags after poll()
[metze/samba/wip.git] / lib / tevent / tevent_poll.c
index 9e2cf1ee05cdf85eb2aca4a8352bc3b438304e54..aa4c50c0c5ec39651d4eea2224d2043ad325ce45 100644 (file)
@@ -499,9 +499,9 @@ static int poll_event_loop_poll(struct tevent_context *ev,
                ev->additional_data, struct poll_event_context);
        int pollrtn;
        int timeout = -1;
-       unsigned first_fd;
-       unsigned i, next_i;
        int poll_errno;
+       struct tevent_fd *fde = NULL;
+       unsigned i;
 
        if (ev->signal_events && tevent_common_check_signal(ev)) {
                return 0;
@@ -541,41 +541,41 @@ static int poll_event_loop_poll(struct tevent_context *ev,
                return 0;
        }
 
-       first_fd = (poll_ev->signal_fd != -1) ? 1 : 0;
-
        /* at least one file descriptor is ready - check
           which ones and call the handler, being careful to allow
           the handler to remove itself when called */
 
-       for (i=first_fd; i<poll_ev->num_fds; i = next_i) {
+       for (fde = ev->fd_events; fde; fde = fde->next) {
+               unsigned idx = fde->additional_flags;
                struct pollfd *pfd;
-               struct tevent_fd *fde;
                uint16_t flags = 0;
 
-               next_i = i + 1;
+               if (idx == UINT64_MAX) {
+                       continue;
+               }
+
+               pfd = &poll_ev->fds[idx];
 
-               fde = poll_ev->fdes[i];
-               if (fde == NULL) {
+               if (pfd->revents & POLLNVAL) {
                        /*
-                        * This fde was talloc_free()'ed. Delete it
-                        * from the arrays
+                        * the socket is dead! this should never
+                        * happen as the socket should have first been
+                        * made readable and that should have removed
+                        * the event, so this must be a bug.
+                        *
+                        * We ignore it here to match the epoll
+                        * behavior.
                         */
-                       poll_ev->num_fds -= 1;
-                       if (poll_ev->num_fds == i) {
-                               break;
-                       }
-                       poll_ev->fds[i] = poll_ev->fds[poll_ev->num_fds];
-                       poll_ev->fdes[i] = poll_ev->fdes[poll_ev->num_fds];
-                       if (poll_ev->fdes[i] != NULL) {
-                               poll_ev->fdes[i]->additional_flags = i;
-                       }
-                       /* we have to reprocess position 'i' */
-                       next_i = i;
+                       tevent_debug(ev, TEVENT_DEBUG_ERROR,
+                                    "POLLNVAL on fde[%p] fd[%d] - disabling\n",
+                                    fde, pfd->fd);
+                       poll_ev->fdes[idx] = NULL;
+                       poll_ev->deleted = true;
+                       DLIST_REMOVE(ev->fd_events, fde);
+                       fde->event_ctx = NULL;
                        continue;
                }
 
-               pfd = &poll_ev->fds[i];
-
                if (pfd->revents & (POLLHUP|POLLERR)) {
                        /* If we only wait for TEVENT_FD_WRITE, we
                           should not tell the event handler about it,
@@ -594,9 +594,37 @@ static int poll_event_loop_poll(struct tevent_context *ev,
                if (pfd->revents & POLLOUT) {
                        flags |= TEVENT_FD_WRITE;
                }
+               /*
+                * Note that fde->flags could be changed when using
+                * the poll_mt backend together with threads,
+                * that why we need to check pfd->revents and fde->flags
+                */
+               flags &= fde->flags;
                if (flags != 0) {
                        fde->handler(ev, fde, flags, fde->private_data);
-                       break;
+                       return 0;
+               }
+       }
+
+       for (i = 0; i < poll_ev->num_fds; i++) {
+               if (poll_ev->fds[i].revents & POLLNVAL) {
+                       /*
+                        * the socket is dead! this should never
+                        * happen as the socket should have first been
+                        * made readable and that should have removed
+                        * the event, so this must be a bug or
+                        * a race in the poll_mt usage.
+                        */
+                       fde = poll_ev->fdes[i];
+                       tevent_debug(ev, TEVENT_DEBUG_WARNING,
+                                    "POLLNVAL on dangling fd[%d] fde[%p] - disabling\n",
+                                    poll_ev->fds[i].fd, fde);
+                       poll_ev->fdes[i] = NULL;
+                       poll_ev->deleted = true;
+                       if (fde != NULL) {
+                               DLIST_REMOVE(ev->fd_events, fde);
+                               fde->event_ctx = NULL;
+                       }
                }
        }