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
rpl_select(int nfds,fd_set * readfds,fd_set * writefds,fd_set * exceptfds,struct timeval * timeout)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