Home | History | Annotate | Line # | Download | only in lib
      1 /* Work around the bug in Solaris 7 whereby a fd that is opened on
      2    /dev/null will cause select/poll to hang when given a NULL timeout.
      3 
      4    Copyright (C) 2004 Free Software Foundation, Inc.
      5 
      6    This program is free software; you can redistribute it and/or modify
      7    it under the terms of the GNU General Public License as published by
      8    the Free Software Foundation; either version 2, or (at your option)
      9    any later version.
     10 
     11    This program is distributed in the hope that it will be useful,
     12    but WITHOUT ANY WARRANTY; without even the implied warranty of
     13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14    GNU General Public License for more details.
     15 
     16    You should have received a copy of the GNU General Public License
     17    along with this program; if not, write to the Free Software Foundation,
     18    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
     19 #include <sys/cdefs.h>
     20 __RCSID("$NetBSD: sunos57-select.c,v 1.2 2016/05/17 14:00:09 christos Exp $");
     21 
     22 
     23 /* written by Mark D. Baushke */
     24 
     25 /*
     26  * Observed on Solaris 7:
     27  *   If /dev/null is in the readfds set, it will never be marked as
     28  *   ready by the OS. In the case of a /dev/null fd being the only fd
     29  *   in the select set and timeout == NULL, the select will hang.
     30  *   If /dev/null is in the exceptfds set, it will not be set on
     31  *   return from select().
     32  */
     33 #ifdef HAVE_CONFIG_H
     34 # include <config.h>
     35 #endif /* HAVE_CONFIG_H */
     36 
     37 /* The rpl_select function calls the real select. */
     38 #undef select
     39 
     40 #include <stdbool.h>
     41 #include <stdio.h>
     42 #include <sys/types.h>
     43 #include <sys/stat.h>
     44 #include <errno.h>
     45 
     46 #ifdef HAVE_UNISTD_H
     47 # include <unistd.h>
     48 #endif /* HAVE_UNISTD_H */
     49 
     50 #include "minmax.h"
     51 #include "xtime.h"
     52 
     53 static struct stat devnull;
     54 static int devnull_set = -1;
     55 int
     56 rpl_select (int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
     57             struct timeval *timeout)
     58 {
     59   int ret = 0;
     60 
     61   /* Argument checking */
     62   if (nfds < 1 || nfds > FD_SETSIZE)
     63     {
     64       errno = EINVAL;
     65       return -1;
     66     }
     67 
     68   /* Perform the initial stat on /dev/null */
     69   if (devnull_set == -1)
     70     devnull_set = stat ("/dev/null", &devnull);
     71 
     72   if (devnull_set >= 0)
     73     {
     74       int fd;
     75       int maxfd = -1;
     76       fd_set null_rfds, null_wfds;
     77       bool altered = false;	/* Whether we have altered the caller's args.
     78 				 */
     79 
     80       FD_ZERO (&null_rfds);
     81       FD_ZERO (&null_wfds);
     82 
     83       for (fd = 0; fd < nfds; fd++)
     84 	{
     85 	  /* Check the callers bits for interesting fds */
     86 	  bool isread = (readfds && FD_ISSET (fd, readfds));
     87 	  bool isexcept = (exceptfds && FD_ISSET (fd, exceptfds));
     88 	  bool iswrite = (writefds && FD_ISSET (fd, writefds));
     89 
     90 	  /* Check interesting fds against /dev/null */
     91 	  if (isread || iswrite || isexcept)
     92 	    {
     93 	      struct stat sb;
     94 
     95 	      /* Equivalent to /dev/null ? */
     96 	      if (fstat (fd, &sb) >= 0
     97 		  && sb.st_dev == devnull.st_dev
     98 		  && sb.st_ino == devnull.st_ino
     99 		  && sb.st_mode == devnull.st_mode
    100 		  && sb.st_uid == devnull.st_uid
    101 		  && sb.st_gid == devnull.st_gid
    102 		  && sb.st_size == devnull.st_size
    103 		  && sb.st_blocks == devnull.st_blocks
    104 		  && sb.st_blksize == devnull.st_blksize)
    105 		{
    106 		  /* Save the interesting bits for later use. */
    107 		  if (isread)
    108 		    {
    109 		      FD_SET (fd, &null_rfds);
    110 		      FD_CLR (fd, readfds);
    111 		      altered = true;
    112 		    }
    113 		  if (isexcept)
    114 		    /* Pass exception bits through.
    115 		     *
    116 		     * At the moment, we only know that this bug
    117 		     * exists in Solaris 7 and so this file should
    118 		     * only be compiled on Solaris 7. Since Solaris 7
    119 		     * never returns ready for exceptions on
    120 		     * /dev/null, we probably could assume this too,
    121 		     * but since Solaris 9 is known to always return
    122 		     * ready for exceptions on /dev/null, pass this
    123 		     * through in case any other systems turn out to
    124 		     * do the same. Besides, this will cause the
    125 		     * timeout to be processed as it would have been
    126 		     * otherwise.
    127 		     */
    128 		    maxfd = MAX (maxfd, fd);
    129 		  if (iswrite)
    130 		    {
    131 		      /* We know of no bugs involving selecting /dev/null
    132 		       * writefds, but we also know that /dev/null is always
    133 		       * ready for write.  Therefore, since we have already
    134 		       * performed all the necessary processing, avoid calling
    135 		       * the system select for this case.
    136 		       */
    137 		      FD_SET (fd, &null_wfds);
    138 		      FD_CLR (fd, writefds);
    139 		      altered = true;
    140 		    }
    141 		}
    142 	      else
    143 		/* A non-/dev/null fd is present. */
    144 		maxfd = MAX (maxfd, fd);
    145 	    }
    146 	}
    147 
    148       if (maxfd >= 0)
    149 	{
    150 	  /* we need to call select, one way or another.  */
    151 	  if (altered)
    152 	    {
    153 	      /* We already have some ready bits set, so timeout immediately
    154 	       * if no bits are set.
    155 	       */
    156 	      struct timeval ztime;
    157 	      ztime.tv_sec = 0;
    158 	      ztime.tv_usec = 0;
    159 	      ret = select (maxfd + 1, readfds, writefds, exceptfds, &ztime);
    160 	      if (ret == 0)
    161 		{
    162 		  /* Timeout.  Zero the sets since the system select might
    163 		   * not have.
    164 		   */
    165 		  if (readfds)
    166 		    FD_ZERO (readfds);
    167 		  if (exceptfds)
    168 		    FD_ZERO (exceptfds);
    169 		  if (writefds)
    170 		    FD_ZERO (writefds);
    171 		}
    172 	    }
    173 	  else
    174 	    /* No /dev/null fds.  Call select just as the user specified.  */
    175 	    ret = select (maxfd + 1, readfds, writefds, exceptfds, timeout);
    176 	}
    177 
    178       /*
    179        * Borrowed from the Solaris 7 man page for select(3c):
    180        *
    181        *   On successful completion, the objects pointed to by the
    182        *   readfds, writefds, and exceptfds arguments are modified to
    183        *   indicate which file descriptors are ready for reading,
    184        *   ready for writing, or have an error condition pending,
    185        *   respectively. For each file descriptor less than nfds, the
    186        *   corresponding bit will be set on successful completion if
    187        *   it was set on input and the associated condition is true
    188        *   for that file descriptor.
    189        *
    190        *   On failure, the objects pointed to by the readfds,
    191        *   writefds, and exceptfds arguments are not modified. If the
    192        *   timeout interval expires without the specified condition
    193        *   being true for any of the specified file descriptors, the
    194        *   objects pointed to by the readfs, writefs, and errorfds
    195        *   arguments have all bits set to 0.
    196        *
    197        *   On successful completion, select() returns the total number
    198        *   of bits set in the bit masks. Otherwise, -1 is returned,
    199        *   and errno is set to indicate the error.
    200        */
    201 
    202       /* Fix up the fd sets for any changes we may have made. */
    203       if (altered)
    204 	{
    205 	  /* Tell the caller that nothing is blocking the /dev/null fds */
    206 	  for (fd = 0; fd < nfds; fd++)
    207 	    {
    208 	      /* If ret < 0, then we still need to restore the fd sets.  */
    209 	      if (FD_ISSET (fd, &null_rfds))
    210 		{
    211 		  FD_SET (fd, readfds);
    212 		  if (ret >= 0)
    213 		    ret++;
    214 		}
    215 	      if (FD_ISSET (fd, &null_wfds))
    216 		{
    217 		  FD_SET (fd, writefds);
    218 		  if (ret >= 0)
    219 		    ret++;
    220 		}
    221 	    }
    222 	}
    223     }
    224   else
    225     ret = select (nfds, readfds, writefds, exceptfds, timeout);
    226 
    227   return ret;
    228 }
    229