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