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