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