select() -> poll(). Bug 2831
authorJeremy Harris <jgh146exb@wizmail.org>
Wed, 17 Nov 2021 17:19:54 +0000 (17:19 +0000)
committerJeremy Harris <jgh146exb@wizmail.org>
Wed, 17 Nov 2021 17:20:17 +0000 (17:20 +0000)
13 files changed:
doc/doc-txt/ChangeLog
src/src/daemon.c
src/src/deliver.c
src/src/exim.c
src/src/expand.c
src/src/functions.h
src/src/ip.c
src/src/malware.c
src/src/receive.c
src/src/smtp_in.c
src/src/spam.c
src/src/transport.c
src/src/transports/smtp.c

index 7f6814d5e906f0f9a5ce28afcd951d0f454d872a..58996c3f8f5c8651210747186e1e2b55cf8ad663 100644 (file)
@@ -40,6 +40,14 @@ JH/09 Fix macro-definition during "-be" expansion testing.  The move to
       write-protected store for macros had not accounted for these runtime
       additions; fix by removing this protection for "-be" mode.
 
+JH/10 Convert all uses of select() to poll().  FreeBSD 12.2 was found to be
+      handing out large-numbered file descriptors, violating the usual Unix
+      assumption (and required by Posix) that the lowest possible number will be
+      allocated by the kernel when a new one is needed.  In the daemon, and any
+      child procesees, values higher than 1024 (being bigger than FD_SETSIZE)
+      are not useable for FD_SET() [and hence select()] and overwrite the stack.
+      Assorted crashes happen.
+
 
 Exim version 4.95
 -----------------
index 0b8d5d5952fc2a7ca86b2ff2defe47159b9b5e8b..a248a4f40a9f9f29a683a8d3d8ebefb63c19baba 100644 (file)
@@ -87,7 +87,7 @@ sigchld_seen = TRUE;
 }
 
 
-/* SIGTERM handler.  Try to get the damon pif file removed
+/* SIGTERM handler.  Try to get the damon pid file removed
 before exiting. */
 
 static void
@@ -141,7 +141,7 @@ Uunlink(s);
 
 static void
 close_daemon_sockets(int daemon_notifier_fd,
-  int * listen_sockets, int listen_socket_count)
+  struct pollfd * fd_polls, int listen_socket_count)
 {
 if (daemon_notifier_fd >= 0)
   {
@@ -152,7 +152,7 @@ if (daemon_notifier_fd >= 0)
 #endif
   }
 
-for (int i = 0; i < listen_socket_count; i++) (void) close(listen_sockets[i]);
+for (int i = 0; i < listen_socket_count; i++) (void) close(fd_polls[i].fd);
 }
 
 
@@ -167,7 +167,7 @@ is required so that they can be closed in the sub-process. Take care not to
 leak store in this process - reset the stacking pool at the end.
 
 Arguments:
-  listen_sockets        sockets which are listening for incoming calls
+  fd_polls        sockets which are listening for incoming calls
   listen_socket_count   count of listening sockets
   accept_socket         socket of the current accepted call
   accepted              socket information about the current call
@@ -176,7 +176,7 @@ Returns:            nothing
 */
 
 static void
-handle_smtp_call(int *listen_sockets, int listen_socket_count,
+handle_smtp_call(struct pollfd *fd_polls, int listen_socket_count,
   int accept_socket, struct sockaddr *accepted)
 {
 pid_t pid;
@@ -459,7 +459,7 @@ if (pid == 0)
   extensive comment before the reception loop in exim.c for a fuller
   explanation of this logic. */
 
-  close_daemon_sockets(daemon_notifier_fd, listen_sockets, listen_socket_count);
+  close_daemon_sockets(daemon_notifier_fd, fd_polls, listen_socket_count);
 
   /* Set FD_CLOEXEC on the SMTP socket. We don't want any rogue child processes
   to be able to communicate with them, under any circumstances. */
@@ -1305,13 +1305,6 @@ return FALSE;
 
 
 
-static void
-add_listener_socket(int fd, fd_set * fds, int * fd_max)
-{
-FD_SET(fd, fds);
-if (fd > *fd_max) *fd_max = fd;
-}
-
 /*************************************************
 *              Exim Daemon Mainline              *
 *************************************************/
@@ -1339,9 +1332,8 @@ void
 daemon_go(void)
 {
 struct passwd * pw;
-int * listen_sockets = NULL;
-int listen_socket_count = 0, listen_fd_max = 0;
-fd_set select_listen;
+struct pollfd * fd_polls, * tls_watch_poll = NULL, * dnotify_poll = NULL;
+int listen_socket_count = 0, poll_fd_count;
 ip_address_item * addresses = NULL;
 time_t last_connection_time = (time_t)0;
 int local_queue_run_max = atoi(CS expand_string(queue_run_max));
@@ -1353,17 +1345,21 @@ debugging lines get the pid added. */
 
 DEBUG(D_any|D_v) debug_selector |= D_pid;
 
-FD_ZERO(&select_listen);
+/* Allocate enough pollstructs for inetd mode plus the ancillary sockets;
+also used when there are no listen sockets. */
+
+fd_polls = store_get(sizeof(struct pollfd) * 3, FALSE);
+
 if (f.inetd_wait_mode)
   {
   listen_socket_count = 1;
-  listen_sockets = store_get(sizeof(int), FALSE);
   (void) close(3);
   if (dup2(0, 3) == -1)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE,
         "failed to dup inetd socket safely away: %s", strerror(errno));
 
-  listen_sockets[0] = 3;
+  fd_polls[0].fd = 3;
+  fd_polls[0].events = POLLIN;
   (void) close(0);
   (void) close(1);
   (void) close(2);
@@ -1390,9 +1386,6 @@ if (f.inetd_wait_mode)
     if (setsockopt(3, IPPROTO_TCP, TCP_NODELAY, US &on, sizeof(on)))
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to set socket NODELAY: %s",
        strerror(errno));
-
-  FD_SET(3, &select_listen);
-  listen_fd_max = 3;
   }
 
 
@@ -1686,11 +1679,16 @@ if (f.daemon_listen && !f.inetd_wait_mode)
         }
     }
 
-  /* Get a vector to remember all the sockets in */
+  /* Get a vector to remember all the sockets in.
+  Two extra elements for the ancillary sockets */
 
   for (ipa = addresses; ipa; ipa = ipa->next)
     listen_socket_count++;
-  listen_sockets = store_get(sizeof(int) * listen_socket_count, FALSE);
+  fd_polls = store_get(sizeof(struct pollfd) * (listen_socket_count + 2),
+                           FALSE);
+  for (struct pollfd * p = fd_polls; p < fd_polls + listen_socket_count + 2;
+       p++)
+    { p->fd = -1; p->events = POLLIN; }
 
   } /* daemon_listen but not inetd_wait_mode */
 
@@ -1795,7 +1793,7 @@ if (f.daemon_listen && !f.inetd_wait_mode)
       wildcard = ipa->address[0] == 0;
       }
 
-    if ((listen_sockets[sk] = fd = ip_socket(SOCK_STREAM, af)) < 0)
+    if ((fd_polls[sk].fd = fd = ip_socket(SOCK_STREAM, af)) < 0)
       {
       if (check_special_case(0, addresses, ipa, FALSE))
         {
@@ -1804,7 +1802,7 @@ if (f.daemon_listen && !f.inetd_wait_mode)
         goto SKIP_SOCKET;
         }
       log_write(0, LOG_PANIC_DIE, "IPv%c socket creation failed: %s",
-        (af == AF_INET6)? '6' : '4', strerror(errno));
+        af == AF_INET6 ? '6' : '4', strerror(errno));
       }
 
     /* If this is an IPv6 wildcard socket, set IPV6_V6ONLY if that option is
@@ -1903,8 +1901,7 @@ if (f.daemon_listen && !f.inetd_wait_mode)
        f.tcp_fastopen_ok = FALSE;
        }
 #endif
-
-      add_listener_socket(fd, &select_listen, &listen_fd_max);
+      fd_polls[sk].fd = fd;
       continue;
       }
 
@@ -2187,14 +2184,21 @@ tls_daemon_init();
 
 /* Add ancillary sockets to the set for select */
 
+poll_fd_count = listen_socket_count;
 #ifndef DISABLE_TLS
 if (tls_watch_fd >= 0)
-  add_listener_socket(tls_watch_fd, &select_listen, &listen_fd_max);
+  {
+  tls_watch_poll = &fd_polls[poll_fd_count++];
+  tls_watch_poll->fd = tls_watch_fd;
+  tls_watch_poll->events = POLLIN;
+  }
 #endif
 if (daemon_notifier_fd >= 0)
-  add_listener_socket(daemon_notifier_fd, &select_listen, &listen_fd_max);
-
-listen_fd_max++;
+  {
+  dnotify_poll = &fd_polls[poll_fd_count++];
+  dnotify_poll->fd = daemon_notifier_fd;
+  dnotify_poll->events = POLLIN;
+  }
 
 /* Close the log so it can be renamed and moved. In the few cases below where
 this long-running process writes to the log (always exceptional conditions), it
@@ -2293,7 +2297,7 @@ for (;;)
           /* Close any open listening sockets in the child */
 
          close_daemon_sockets(daemon_notifier_fd,
-           listen_sockets, listen_socket_count);
+           fd_polls, listen_socket_count);
 
           /* Reset SIGHUP and SIGCHLD in the child in both cases. */
 
@@ -2421,9 +2425,8 @@ for (;;)
 
   if (f.daemon_listen)
     {
-    int check_lsk = 0, lcount;
+    int lcount;
     BOOL select_failed = FALSE;
-    fd_set fds = select_listen;
 
     DEBUG(D_any) debug_printf("Listening...\n");
 
@@ -2440,8 +2443,7 @@ for (;;)
       errno = EINTR;
       }
     else
-      lcount = select(listen_fd_max, (SELECT_ARG2_TYPE *)&fds,
-        NULL, NULL, NULL);
+      lcount = poll(fd_polls, poll_fd_count, -1);
 
     if (lcount < 0)
       {
@@ -2461,15 +2463,15 @@ for (;;)
       handle_ending_processes();
 
 #ifndef DISABLE_TLS
+      {
+      int old_tfd;
       /* Create or rotate any required keys; handle (delayed) filewatch event */
-      for (int old_tfd = tls_daemon_tick(); old_tfd >= 0; )
-       {
-       FD_CLR(old_tfd, &select_listen);
-       if (old_tfd == listen_fd_max - 1) listen_fd_max = old_tfd;
-       if (tls_watch_fd >= 0)
-         add_listener_socket(tls_watch_fd, &select_listen, &listen_fd_max);
-       break;
-       }
+
+      if ((old_tfd = tls_daemon_tick()) >= 0)
+       for (struct pollfd * p = &fd_polls[listen_socket_count];
+            p < fd_polls + poll_fd_count; p++)
+         if (p->fd == old_tfd) { p->fd = tls_watch_fd ; break; }
+      }
 #endif
       errno = select_errno;
       }
@@ -2490,22 +2492,23 @@ for (;;)
       if (!select_failed)
        {
 #if !defined(DISABLE_TLS) && (defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT))
-       if (tls_watch_fd >= 0 && FD_ISSET(tls_watch_fd, &fds))
+       if (tls_watch_poll && tls_watch_poll->revents & POLLIN)
          {
+         tls_watch_poll->revents = 0;
           tls_watch_trigger_time = time(NULL); /* Set up delayed event */
          tls_watch_discard_event(tls_watch_fd);
          break;        /* to top of daemon loop */
          }
 #endif
-       if (daemon_notifier_fd >= 0 && FD_ISSET(daemon_notifier_fd, &fds))
+       if (dnotify_poll && dnotify_poll->revents & POLLIN)
          {
+         dnotify_poll->revents = 0;
          sigalrm_seen = daemon_notification();
          break;        /* to top of daemon loop */
          }
-       while (check_lsk < listen_socket_count)
-         {
-         int lfd = listen_sockets[check_lsk++];
-          if (FD_ISSET(lfd, &fds))
+       for (struct pollfd * p = fd_polls; p < fd_polls + listen_socket_count;
+            p++)
+         if (p->revents & POLLIN)
             {
            EXIM_SOCKLEN_T alen = sizeof(accepted);
 #ifdef TCP_INFO
@@ -2516,23 +2519,23 @@ for (;;)
 
            smtp_listen_backlog = 0;
            if (  smtp_backlog_monitor > 0
-              && getsockopt(lfd, IPPROTO_TCP, TCP_INFO, &ti, &tlen) == 0)
+              && getsockopt(p->fd, IPPROTO_TCP, TCP_INFO, &ti, &tlen) == 0)
              {
 # ifdef EXIM_HAVE_TCPI_UNACKED
              DEBUG(D_interface) debug_printf("listen fd %d queue max %u curr %u\n",
-                     lfd, ti.tcpi_sacked, ti.tcpi_unacked);
+                     p->fd, ti.tcpi_sacked, ti.tcpi_unacked);
              smtp_listen_backlog = ti.tcpi_unacked;
 # elif defined(__FreeBSD__)    /* This does not work. Investigate kernel sourcecode. */
              DEBUG(D_interface) debug_printf("listen fd %d queue max %u curr %u\n",
-                     lfd, ti.__tcpi_sacked, ti.__tcpi_unacked);
+                     p->fd, ti.__tcpi_sacked, ti.__tcpi_unacked);
              smtp_listen_backlog = ti.__tcpi_unacked;
 # endif
              }
 #endif
-            accept_socket = accept(lfd, (struct sockaddr *)&accepted, &alen);
+           p->revents = 0;
+            accept_socket = accept(p->fd, (struct sockaddr *)&accepted, &alen);
             break;
             }
-         }
        }
 
       /* If select or accept has failed and this was not caused by an
@@ -2591,7 +2594,7 @@ for (;;)
 #endif
         if (inetd_wait_timeout)
           last_connection_time = time(NULL);
-        handle_smtp_call(listen_sockets, listen_socket_count, accept_socket,
+        handle_smtp_call(fd_polls, listen_socket_count, accept_socket,
           (struct sockaddr *)&accepted);
         }
       }
@@ -2606,10 +2609,8 @@ for (;;)
 
   else
     {
-    struct timeval tv;
-    tv.tv_sec = queue_interval;
-    tv.tv_usec = 0;
-    select(0, NULL, NULL, NULL, &tv);
+    struct pollfd p;
+    poll(&p, 0, queue_interval * 1000);
     handle_ending_processes();
     }
 
@@ -2634,8 +2635,7 @@ for (;;)
     {
     log_write(0, LOG_MAIN, "pid %d: SIGHUP received: re-exec daemon",
       getpid());
-    close_daemon_sockets(daemon_notifier_fd,
-      listen_sockets, listen_socket_count);
+    close_daemon_sockets(daemon_notifier_fd, fd_polls, listen_socket_count);
     ALARM_CLR(0);
     signal(SIGHUP, SIG_IGN);
     sighup_argv[0] = exim_path;
index 4594c4a1d95a6703856e7f498f1b6edbaa0912c5..8aad811c61679a7a2a75e8ded709af5c1bd21ae8 100644 (file)
@@ -74,6 +74,7 @@ static BOOL update_spool;
 static BOOL remove_journal;
 static int  parcount = 0;
 static pardata *parlist = NULL;
+static struct pollfd *parpoll;
 static int  return_count;
 static uschar *frozen_info = US"";
 static uschar *used_return_path = NULL;
@@ -3306,7 +3307,7 @@ BOOL done = p->done;
 
 /* Loop through all items, reading from the pipe when necessary. The pipe
 used to be non-blocking. But I do not see a reason for using non-blocking I/O
-here, as the preceding select() tells us, if data is available for reading.
+here, as the preceding poll() tells us, if data is available for reading.
 
 A read() on a "selected" handle should never block, but(!) it may return
 less data then we expected. (The buffer size we pass to read() shouldn't be
@@ -3840,7 +3841,7 @@ static address_item *
 par_wait(void)
 {
 int poffset, status;
-address_item *addr, *addrlist;
+address_item * addr, * addrlist;
 pid_t pid;
 
 set_process_info("delivering %s: waiting for a remote delivery subprocess "
@@ -3850,18 +3851,18 @@ set_process_info("delivering %s: waiting for a remote delivery subprocess "
 existence - in which case give an error return. We cannot proceed just by
 waiting for a completion, because a subprocess may have filled up its pipe, and
 be waiting for it to be emptied. Therefore, if no processes have finished, we
-wait for one of the pipes to acquire some data by calling select(), with a
+wait for one of the pipes to acquire some data by calling poll(), with a
 timeout just in case.
 
 The simple approach is just to iterate after reading data from a ready pipe.
 This leads to non-ideal behaviour when the subprocess has written its final Z
 item, closed the pipe, and is in the process of exiting (the common case). A
-call to waitpid() yields nothing completed, but select() shows the pipe ready -
+call to waitpid() yields nothing completed, but poll() shows the pipe ready -
 reading it yields EOF, so you end up with busy-waiting until the subprocess has
 actually finished.
 
 To avoid this, if all the data that is needed has been read from a subprocess
-after select(), an explicit wait() for it is done. We know that all it is doing
+after poll(), an explicit wait() for it is done. We know that all it is doing
 is writing to the pipe and then exiting, so the wait should not be long.
 
 The non-blocking waitpid() is to some extent just insurance; if we could
@@ -3881,9 +3882,7 @@ for (;;)   /* Normally we do not repeat this loop */
   {
   while ((pid = waitpid(-1, &status, WNOHANG)) <= 0)
     {
-    struct timeval tv;
-    fd_set select_pipes;
-    int maxpipe, readycount;
+    int readycount;
 
     /* A return value of -1 can mean several things. If errno != ECHILD, it
     either means invalid options (which we discount), or that this process was
@@ -3907,7 +3906,7 @@ for (;;)   /* Normally we do not repeat this loop */
     subprocesses are still in existence. If kill() gives an OK return, we know
     it must be for one of our processes - it can't be for a re-use of the pid,
     because if our process had finished, waitpid() would have found it. If any
-    of our subprocesses are in existence, we proceed to use select() as if
+    of our subprocesses are in existence, we proceed to use poll() as if
     waitpid() had returned zero. I think this is safe. */
 
     if (pid < 0)
@@ -3931,7 +3930,7 @@ for (;;)   /* Normally we do not repeat this loop */
       if (poffset >= remote_max_parallel)
         {
         DEBUG(D_deliver) debug_printf("*** no delivery children found\n");
-        return NULL;   /* This is the error return */
+       return NULL;    /* This is the error return */
         }
       }
 
@@ -3940,28 +3939,23 @@ for (;;)   /* Normally we do not repeat this loop */
     subprocess, but there are no completed subprocesses. See if any pipes are
     ready with any data for reading. */
 
-    DEBUG(D_deliver) debug_printf("selecting on subprocess pipes\n");
+    DEBUG(D_deliver) debug_printf("polling subprocess pipes\n");
 
-    maxpipe = 0;
-    FD_ZERO(&select_pipes);
     for (poffset = 0; poffset < remote_max_parallel; poffset++)
       if (parlist[poffset].pid != 0)
-        {
-        int fd = parlist[poffset].fd;
-        FD_SET(fd, &select_pipes);
-        if (fd > maxpipe) maxpipe = fd;
-        }
+       {
+       parpoll[poffset].fd = parlist[poffset].fd;
+       parpoll[poffset].events = POLLIN;
+       }
+      else
+       parpoll[poffset].fd = -1;
 
     /* Stick in a 60-second timeout, just in case. */
 
-    tv.tv_sec = 60;
-    tv.tv_usec = 0;
-
-    readycount = select(maxpipe + 1, (SELECT_ARG2_TYPE *)&select_pipes,
-         NULL, NULL, &tv);
+    readycount = poll(parpoll, remote_max_parallel, 60 * 1000);
 
     /* Scan through the pipes and read any that are ready; use the count
-    returned by select() to stop when there are no more. Select() can return
+    returned by poll() to stop when there are no more. Select() can return
     with no processes (e.g. if interrupted). This shouldn't matter.
 
     If par_read_pipe() returns TRUE, it means that either the terminating Z was
@@ -3978,7 +3972,7 @@ for (;;)   /* Normally we do not repeat this loop */
          poffset++)
       {
       if (  (pid = parlist[poffset].pid) != 0
-         && FD_ISSET(parlist[poffset].fd, &select_pipes)
+        && parpoll[poffset].revents
         )
         {
         readycount--;
@@ -4016,7 +4010,7 @@ for (;;)   /* Normally we do not repeat this loop */
     "transport process list", pid);
   }  /* End of the "for" loop */
 
-/* Come here when all the data was completely read after a select(), and
+/* Come here when all the data was completely read after a poll(), and
 the process in pid has been wait()ed for. */
 
 PROCESS_DONE:
@@ -4051,7 +4045,7 @@ if ((status & 0xffff) != 0)
     "%s %d",
     addrlist->transport->driver_name,
     status,
-    (msb == 0)? "terminated by signal" : "exit code",
+    msb == 0 ? "terminated by signal" : "exit code",
     code);
 
   if (msb != 0 || (code != SIGTERM && code != SIGKILL && code != SIGQUIT))
@@ -4069,7 +4063,8 @@ if ((status & 0xffff) != 0)
 /* Else complete reading the pipe to get the result of the delivery, if all
 the data has not yet been obtained. */
 
-else if (!parlist[poffset].done) (void)par_read_pipe(poffset, TRUE);
+else if (!parlist[poffset].done)
+  (void) par_read_pipe(poffset, TRUE);
 
 /* Put the data count and return path into globals, mark the data slot unused,
 decrement the count of subprocesses, and return the address chain. */
@@ -4218,6 +4213,7 @@ if (!parlist)
   parlist = store_get(remote_max_parallel * sizeof(pardata), FALSE);
   for (poffset = 0; poffset < remote_max_parallel; poffset++)
     parlist[poffset].pid = 0;
+  parpoll = store_get(remote_max_parallel * sizeof(struct pollfd), FALSE);
   }
 
 /* Now loop for each remote delivery */
@@ -4613,7 +4609,7 @@ nonmatch domains
     that it can use either of them, though it prefers O_NONBLOCK, which
     distinguishes between EOF and no-more-data. */
 
-/* The data appears in a timely manner and we already did a select on
+/* The data appears in a timely manner and we already did a poll on
 all pipes, so I do not see a reason to use non-blocking IO here
 
 #ifdef O_NONBLOCK
index 133761de93d236bfb09cc594c7433d0dc49a854c..42db457c0e80d6c74462423d5aa26437168148a6 100644 (file)
@@ -5735,13 +5735,8 @@ for (BOOL more = TRUE; more; )
     the file copy. */
 
     if (!receive_timeout)
-      {
-      struct timeval t = { .tv_sec = 30*60, .tv_usec = 0 };    /* 30 minutes */
-      fd_set r;
-
-      FD_ZERO(&r); FD_SET(0, &r);
-      if (select(1, &r, NULL, NULL, &t) == 0) mainlog_close();
-      }
+      if (poll_one_fd(0, POLLIN, 30*60*1000) == 0)     /* 30 minutes */
+       mainlog_close();
 
     /* Read the data for the message. If filter_test is not FTEST_NONE, this
     will just read the headers for the message, and not write anything onto the
index 59554840e4f60eb620ca461b7bdda423c776ab2c..bfae2a3c051b3d9e3903b01baeb2063d0bb29fae 100644 (file)
@@ -1760,8 +1760,6 @@ const uschar * where;
 #ifndef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS
 uschar * sname;
 #endif
-fd_set fds;
-struct timeval tv;
 
 if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0)
   {
@@ -1805,9 +1803,7 @@ if (connect(fd, (const struct sockaddr *)&sa_un, len) < 0)
 buf[0] = NOTIFY_QUEUE_SIZE_REQ;
 if (send(fd, buf, 1, 0) < 0) { where = US"send"; goto bad; }
 
-FD_ZERO(&fds); FD_SET(fd, &fds);
-tv.tv_sec = 2; tv.tv_usec = 0;
-if (select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tv) != 1)
+if (poll_one_fd(fd, POLLIN, 2 * 1000) != 1)
   {
   DEBUG(D_expand) debug_printf("no daemon response; using local evaluation\n");
   len = snprintf(CS buf, sizeof(buf), "%u", queue_count_cached());
index 3dd890a00fd1feefa27cbb2cc257456737315f71..0cf80dfbb94ebf9c450d8d28f27efb75cbad7c41 100644 (file)
@@ -1255,6 +1255,13 @@ child_open(uschar **argv, uschar **envp, int newumask, int *infdptr,
   outfdptr, make_leader, purpose);
 }
 
+static inline int
+poll_one_fd(int fd, short pollbits, int tmo_millisec)
+{
+struct pollfd p = {.fd = fd, .events = pollbits};
+return poll(&p, 1, tmo_millisec);
+}
+
 # endif        /* !COMPILE_UTILITY */
 
 /******************************************************************************/
index d83d6f91046470c41f9b6ec8fde57a65a9b22991..aa42343fbc7ea7365dc04ef90a2943459e13fa4d 100644 (file)
@@ -589,9 +589,7 @@ Returns:      TRUE => ready for i/o
 BOOL
 fd_ready(int fd, time_t timelimit)
 {
-fd_set select_inset;
-int time_left = timelimit - time(NULL);
-int rc;
+int rc, time_left = timelimit - time(NULL);
 
 if (time_left <= 0)
   {
@@ -602,12 +600,8 @@ if (time_left <= 0)
 
 do
   {
-  struct timeval tv = { .tv_sec = time_left, .tv_usec = 0 };
-  FD_ZERO (&select_inset);
-  FD_SET (fd, &select_inset);
-
   /*DEBUG(D_transport) debug_printf("waiting for data on fd\n");*/
-  rc = select(fd + 1, (SELECT_ARG2_TYPE *)&select_inset, NULL, NULL, &tv);
+  rc = poll_one_fd(fd, POLLIN, time_left * 1000);
 
   /* If some interrupt arrived, just retry. We presume this to be rare,
   but it can happen (e.g. the SIGUSR1 signal sent by exiwhat causes
@@ -636,7 +630,7 @@ do
   /* Checking the FD_ISSET is not enough, if we're interrupted, the
   select_inset may still contain the 'input'. */
   }
-while (rc < 0 || !FD_ISSET(fd, &select_inset));
+while (rc < 0);
 return TRUE;
 }
 
index 10a390dfa4d222ff7a69ade503e66a3677e4f135..d9ab3b9dd19658a584da824d4b04f85ec2857b57 100644 (file)
@@ -277,11 +277,7 @@ int fd = ip_connectedsocket(SOCK_STREAM, hostname, port, port, 5,
 /* Under some fault conditions, FreeBSD 12.2 seen to send a (non-TFO) SYN
 and, getting no response, wait for a long time.  Impose a 5s max. */
 if (fd >= 0)
-  {
-  struct timeval tv = {.tv_sec = 5};
-  fd_set fds;
-  FD_ZERO(&fds); FD_SET(fd, &fds); (void) select(fd+1, NULL, &fds, NULL, &tv);
-  }
+  (void) poll_one_fd(fd, POLLOUT, 5 * 1000);
 #endif
 return fd;
 }
index fab0f00c467eeb2da0030d718a2044b43bb9dfcb..3adcbbd88b19de4344dec4e36a78759cedca3464 100644 (file)
@@ -624,12 +624,8 @@ if (!receive_timeout && !receive_hasc())
   if (t.tv_sec > 30*60)
     mainlog_close();
   else
-    {
-    fd_set r;
-    FD_ZERO(&r); FD_SET(0, &r);
-    t.tv_sec = 30*60 - t.tv_sec; t.tv_usec = 0;
-    if (select(1, &r, NULL, NULL, &t) == 0) mainlog_close();
-    }
+    if (poll_one_fd(0, POLLIN, (30*60 - t.tv_sec) * 1000) == 0)
+      mainlog_close();
   }
 }
 
@@ -4234,12 +4230,7 @@ response, but the chance of this happening should be small. */
 if (smtp_input && sender_host_address && !f.sender_host_notsocket &&
     !receive_smtp_buffered())
   {
-  struct timeval tv = {.tv_sec = 0, .tv_usec = 0};
-  fd_set select_check;
-  FD_ZERO(&select_check);
-  FD_SET(fileno(smtp_in), &select_check);
-
-  if (select(fileno(smtp_in) + 1, &select_check, NULL, NULL, &tv) != 0)
+  if (poll_one_fd(fileno(smtp_in), POLLIN, 0) != 0)
     {
     int c = (receive_getc)(GETC_BUFFER_UNLIMITED);
     if (c != EOF) (receive_ungetc)(c); else
index 824178e4d6f7a7c6bddefdd12a7a43795441aff6..7cb966f24afa5a8bbecd567af28d29f1d4264afe 100644 (file)
@@ -346,8 +346,6 @@ static BOOL
 wouldblock_reading(void)
 {
 int fd, rc;
-fd_set fds;
-struct timeval tzero = {.tv_sec = 0, .tv_usec = 0};
 
 #ifndef DISABLE_TLS
 if (tls_in.active.sock >= 0)
@@ -358,9 +356,7 @@ if (smtp_inptr < smtp_inend)
   return FALSE;
 
 fd = fileno(smtp_in);
-FD_ZERO(&fds);
-FD_SET(fd, &fds);
-rc = select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tzero);
+rc = poll_one_fd(fd, POLLIN, 0);
 
 if (rc <= 0) return TRUE;     /* Not ready to read */
 rc = smtp_getc(GETC_BUFFER_UNLIMITED);
@@ -3942,16 +3938,8 @@ log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
 /* Pause, hoping client will FIN first so that they get the TIME_WAIT.
 The socket should become readble (though with no data) */
 
-  {
-  int fd = fileno(smtp_in);
-  fd_set fds;
-  struct timeval t_limit = {.tv_sec = 0, .tv_usec = 200*1000};
-
-  FD_ZERO(&fds);
-  FD_SET(fd, &fds);
-  (void) select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &t_limit);
-  }
-#endif /*!DAEMON_CLOSE_NOWAIT*/
+(void) poll_one_fd(fileno(smtp_in), POLLIN, 200);
+#endif /*!SERVERSIDE_CLOSE_NOWAIT*/
 }
 
 
index 470e5fae7c1e2e67892000d80e5e138b45423d7a..e3316ed968f4912e04dc253c1d2ac18570b7dd9d 100644 (file)
@@ -194,12 +194,6 @@ uschar *p,*q;
 int override = 0;
 time_t start;
 size_t read, wrote;
-#ifndef NO_POLL_H
-struct pollfd pollfd;
-#else                               /* Patch posted by Erik ? for OS X */
-struct timeval select_tv;         /* and applied by PH */
-fd_set select_fd;
-#endif
 uschar *spamd_address_work;
 spamd_address_container * sd;
 
@@ -395,19 +389,19 @@ if (wrote == -1)
   }
 
 /* now send the file */
-/* spamd sometimes accepts connections but doesn't read data off
- * the connection.  We make the file descriptor non-blocking so
- * that the write will only write sufficient data without blocking
- * and we poll the descriptor to make sure that we can write without
- * blocking.  Short writes are gracefully handled and if the whole
- * transaction takes too long it is aborted.
- * Note: poll() is not supported in OSX 10.2 and is reported to be
*       broken in more recent versions (up to 10.4).
+/* spamd sometimes accepts connections but doesn't read data off the connection.
+We make the file descriptor non-blocking so that the write will only write
+sufficient data without blocking and we poll the descriptor to make sure that we
+can write without blocking.  Short writes are gracefully handled and if the
+whole transaction takes too long it is aborted.
+
+Note: poll() is not supported in OSX 10.2 and is reported to be broken in more
     recent versions (up to 10.4). Workaround using select() removed 2021/11 (jgh).
  */
-#ifndef NO_POLL_H
-pollfd.fd = spamd_cctx.sock;
-pollfd.events = POLLOUT;
+#ifdef NO_POLL_H
+# error Need poll(2) support
 #endif
+
 (void)fcntl(spamd_cctx.sock, F_SETFL, O_NONBLOCK);
 do
   {
@@ -416,19 +410,7 @@ do
     {
     offset = 0;
 again:
-#ifndef NO_POLL_H
-    result = poll(&pollfd, 1, 1000);
-
-/* Patch posted by Erik ? for OS X and applied by PH */
-#else
-    select_tv.tv_sec = 1;
-    select_tv.tv_usec = 0;
-    FD_ZERO(&select_fd);
-    FD_SET(spamd_cctx.sock, &select_fd);
-    result = select(spamd_cctx.sock+1, NULL, &select_fd, NULL, &select_tv);
-#endif
-/* End Erik's patch */
-
+    result = poll_one_fd(spamd_cctx.sock, POLLOUT, 1000);
     if (result == -1 && errno == EINTR)
       goto again;
     else if (result < 1)
index 8c74030f04f1e5f751489fd2eebf5822b1d112a0..ef523657ea05b36f122ade55b8c838a22e9d2a50 100644 (file)
@@ -253,7 +253,6 @@ for (int i = 0; i < 100; i++)
 
   for(;;)
     {
-    fd_set fds;
     /* This code makes use of alarm() in order to implement the timeout. This
     isn't a very tidy way of doing things. Using non-blocking I/O with select()
     provides a neater approach. However, I don't know how to do this when TLS is
@@ -281,8 +280,7 @@ for (int i = 0; i < 100; i++)
     if (rc >= 0 || errno != ENOTCONN || connretry <= 0)
       break;
 
-    FD_ZERO(&fds); FD_SET(fd, &fds);
-    select(fd+1, NULL, &fds, NULL, NULL);      /* could set timout? */
+    poll_one_fd(fd, POLLOUT, -1);              /* could set timeout? retval check? */
     connretry--;
     }
 
index d321bd69ecc2413068c7aca5723658594ba94795..c64bb7010cffa763156dd79f591db236172aeb96 100644 (file)
@@ -3550,8 +3550,8 @@ void
 smtp_proxy_tls(void * ct_ctx, uschar * buf, size_t bsize, int * pfd,
   int timeout)
 {
-fd_set rfds, efds;
-int max_fd = MAX(pfd[0], tls_out.active.sock) + 1;
+struct pollfd p[2] = {{.fd = tls_out.active.sock, .events = POLLIN},
+                     {.fd = pfd[0], .events = POLLIN}};
 int rc, i;
 BOOL send_tls_shutdown = TRUE;
 
@@ -3560,23 +3560,16 @@ if ((rc = exim_fork(US"tls-proxy")))
   _exit(rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
 
 set_process_info("proxying TLS connection for continued transport");
-FD_ZERO(&rfds);
-FD_SET(tls_out.active.sock, &rfds);
-FD_SET(pfd[0], &rfds);
 
-for (int fd_bits = 3; fd_bits; )
+do
   {
   time_t time_left = timeout;
   time_t time_start = time(NULL);
 
   /* wait for data */
-  efds = rfds;
   do
     {
-    struct timeval tv = { time_left, 0 };
-
-    rc = select(max_fd,
-      (SELECT_ARG2_TYPE *)&rfds, NULL, (SELECT_ARG2_TYPE *)&efds, &tv);
+    rc = poll(p, 2, time_left * 1000);
 
     if (rc < 0 && errno == EINTR)
       if ((time_left -= time(NULL) - time_start) > 0) continue;
@@ -3589,23 +3582,22 @@ for (int fd_bits = 3; fd_bits; )
 
     /* For errors where not readable, bomb out */
 
-    if (FD_ISSET(tls_out.active.sock, &efds) || FD_ISSET(pfd[0], &efds))
+    if (p[0].revents & POLLERR || p[1].revents & POLLERR)
       {
       DEBUG(D_transport) debug_printf("select: exceptional cond on %s fd\n",
-       FD_ISSET(pfd[0], &efds) ? "proxy" : "tls");
-      if (!(FD_ISSET(tls_out.active.sock, &rfds) || FD_ISSET(pfd[0], &rfds)))
+       p[0].revents & POLLERR ? "tls" : "proxy");
+      if (!(p[0].revents & POLLIN || p[1].events & POLLIN))
        goto done;
       DEBUG(D_transport) debug_printf("- but also readable; no exit yet\n");
       }
     }
-  while (rc < 0 || !(FD_ISSET(tls_out.active.sock, &rfds) || FD_ISSET(pfd[0], &rfds)));
+  while (rc < 0 || !(p[0].revents & POLLIN || p[1].revents & POLLIN));
 
   /* handle inbound data */
-  if (FD_ISSET(tls_out.active.sock, &rfds))
+  if (p[0].revents & POLLIN)
     if ((rc = tls_read(ct_ctx, buf, bsize)) <= 0)      /* Expect -1 for EOF; */
     {                              /* that reaps the TLS Close Notify record */
-      fd_bits &= ~1;
-      FD_CLR(tls_out.active.sock, &rfds);
+      p[0].fd = -1;
       shutdown(pfd[0], SHUT_WR);
       timeout = 5;
       }
@@ -3616,11 +3608,10 @@ for (int fd_bits = 3; fd_bits; )
   /* Handle outbound data.  We cannot combine payload and the TLS-close
   due to the limitations of the (pipe) channel feeding us.  Maybe use a unix-domain
   socket? */
-  if (FD_ISSET(pfd[0], &rfds))
+  if (p[1].revents & POLLIN)
     if ((rc = read(pfd[0], buf, bsize)) <= 0)
       {
-      fd_bits &= ~2;
-      FD_CLR(pfd[0], &rfds);
+      p[1].fd = -1;
 
 # ifdef EXIM_TCP_CORK  /* Use _CORK to get TLS Close Notify in FIN segment */
       (void) setsockopt(tls_out.active.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on));
@@ -3633,10 +3624,8 @@ for (int fd_bits = 3; fd_bits; )
       for (int nbytes = 0; rc - nbytes > 0; nbytes += i)
        if ((i = tls_write(ct_ctx, buf + nbytes, rc - nbytes, FALSE)) < 0)
          goto done;
-
-  if (fd_bits & 1) FD_SET(tls_out.active.sock, &rfds);
-  if (fd_bits & 2) FD_SET(pfd[0], &rfds);
   }
+while (p[0].fd >= 0 || p[1].fd >= 0);
 
 done:
   if (send_tls_shutdown) tls_close(ct_ctx, TLS_SHUTDOWN_NOWAIT);