Solaris 7 select() may hang on a /dev/null fd when timeout == NULL
"Mark D. Baushke" <[email protected]> Tue, 19 Oct 2004 15:08:20 -0700
| Newsgroups | gmane.comp.lib.gnulib.bugs,gmane.comp.version-control.cvs.bugs |
|---|---|
| Message-ID | <[email protected]> |
Greetings, While working on the top-of-tree version of CVS, I ran into the problem that is described here: http://sunsolve.sun.com/search/printfriendly.do?assetkey=1-1-1171830-1 (hmmm... the URL seems unreachable right now, I will include a copy of it in the set of attachments below.) Briefly, Solaris 7 systems have a bug when select() is called with only "/dev/null" readfds and a NULL value for the timeout. The workaround is to avoid calling select() in this case and just to return with the readfds updated to include the count of descriptors. Again on Solaris 7 systems, if "/dev/null" is one of the readfds and there are other non-"/dev/null" fds in the fd sets being passed, the "/dev/null" fd will never be set on return from select(). Similarly, any "/dev/null" fds that were part of the exceptfds will also be zero on return. On Solaris 9, "/dev/null" fds always show up in the returning fd set as being available. I am given to understand that Solaris 2.6 may the hang problem, but I do not have such a system to test at present. I believe that the attached files: lib/select.c m4/select.m4 modules/select may approimate the standards for inclusion in the GNULIB. I would like to see it or something like it added to the GNULIB project. Your advice as to automake macro naming and format as well as the implementation of the workaround would be much appreciated. Note: It could be argued that having "/dev/null" in the exceptfds does not require any special handling as there will never be any exceptions to service. My application does not need it, so I am willing to take your advice in the implementation. Thank you, -- Mark _______________________________________________ Bug-gnulib mailing list [email protected] http://lists.gnu.org/mailman/listinfo/bug-gnulib
select.c
(text/x-c, 4.7 KB)
/* Work around the bug in Solaris 7 whereby a fd that is opened on
/dev/null will cause select/poll to hang when given a NULL timeout.
Copyright (C) 2004 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
/* written by Mark D. Baushke */
/*
* Observed on Solaris 7:
* If /dev/null is in the readfds set, it will never be marked as
* ready by the OS. In the case of a /dev/null fd being the only fd
* in the select set and timeout == NULL, the select will hang.
* If /dev/null is in the exceptfds set, it will not be set on
* return from select().
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif /* HAVE_CONFIG_H */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif /* HAVE_UNISTD_H */
#include "xtime.h"
/* The rpl_select function calls the real select. */
#undef select
static struct stat devnull;
static int devnull_set = -1;
int
rpl_select (int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
struct timeval *timeout)
{
int ret = 0;
/* Argument checking */
if (nfds < 1 || nfds > FD_SETSIZE)
{
errno = EINVAL;
return -1;
}
/* Perform the initial stat on /dev/null */
if (devnull_set == -1)
devnull_set = stat ("/dev/null", &devnull);
if (devnull_set >= 0)
{
int fd;
fd_set null_rfds, null_efds;
int altered = 0; /* have the callers args have been modified? */
int count = 0; /* Count of /dev/null fds in all sets. */
int goodfd = 0; /* A non-/dev/null fd means call select() */
FD_ZERO (&null_rfds);
FD_ZERO (&null_efds);
for (fd = 0; fd < nfds; fd++)
{
/* Check the callers bits for interesting fds */
int isread = (readfds && FD_ISSET (fd, readfds));
int iswrite = (writefds && FD_ISSET (fd, writefds));
int isexcept = (exceptfds && FD_ISSET (fd, exceptfds));
/* Check interesting fds against /dev/null */
if (isread || iswrite || isexcept)
{
struct stat sb;
/* Equivalent to /dev/null ? */
if (fstat (fd, &sb) >= 0
&& sb.st_dev == devnull.st_dev
&& sb.st_ino == devnull.st_ino
&& sb.st_mode == devnull.st_mode
&& sb.st_uid == devnull.st_uid
&& sb.st_gid == devnull.st_gid
&& sb.st_size == devnull.st_size
&& sb.st_blocks == devnull.st_blocks
&& sb.st_blksize == devnull.st_blksize)
{
/* Save the interesting bits for later use. */
if (readfds != NULL && isread)
{
FD_SET (fd, &null_rfds);
FD_CLR (fd, readfds);
count++;
altered = 1; /* We have made changes to args. */
}
if (writefds != NULL && iswrite)
goodfd = 1;
if (exceptfds != NULL && isexcept)
{
FD_SET (fd, &null_efds);
FD_CLR (fd, exceptfds);
count++;
altered = 1; /* We have made changes to args. */
}
}
else
goodfd = 1; /* A non-/dev/null fd is present. */
}
}
/* Call select() if there is anything left after alterations.
* Note: We may end up waiting for the entire duration of the
* timeout even though the /dev/null fds might be ready right
* now. Those are the breaks if we ever want to see any of the
* other fds be ready.
*/
if (goodfd)
ret = select (nfds, readfds, writefds, exceptfds, timeout);
/* Fix up the fd sets for any changes we may have made. */
if (altered)
{
/* Add to the number of ready fds in all sets.
* If ret < 0, then we still need to restore the fd sets,
* but we should make sure that we do not add any /dev/null
* fds to the count or things could get confused.
*/
if (ret >= 0)
ret += count;
/* Tell the caller that nothing is blocking the /dev/null fds */
for (fd = 0; fd < nfds; fd++)
{
if (readfds != NULL && FD_ISSET (fd, &null_rfds))
FD_SET (fd, readfds);
if (exceptfds != NULL && FD_ISSET (fd, &null_efds))
FD_SET (fd, exceptfds);
}
}
}
else
ret = select (nfds, readfds, writefds, exceptfds, timeout);
return ret;
}
select.m4
(text/x-m4, 1.8 KB)
dnl From Mark D Baushke.
dnl
dnl See if select() on a /dev/null fd hangs when timeout is NULL.
dnl Also check to see that /dev/null is in the readfds set returned.
dnl
dnl Observed on Solaris 7:
dnl If /dev/null is in the readfds set, it will never be marked as
dnl ready by the OS. In the case of a /dev/null fd being the only fd
dnl in the select set and timeout == NULL, the select will hang.
dnl
dnl If the test fails, then arrange to use select only via a wrapper
dnl function that works around the problem.
AC_DEFUN([ccvs_FUNC_SELECT],
[
AC_CACHE_CHECK([whether select hangs on /dev/null fd when timeout is NULL],
ccvs_cv_func_select_hang,
[AC_RUN_IFELSE([AC_LANG_PROGRAM([[
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif /* HAVE_UNISTD_H */
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif
#include <errno.h>]], [[
pid_t pid;
int status;
pid = fork();
if (pid == 0)
{
int numfds;
fd_set readfds;
int fd = open ("/dev/null", O_RDONLY);
FD_ZERO (&readfds);
FD_SET (fd, &readfds);
numfds = select (fd + 1, &readfds, NULL, NULL, NULL);
if ((numfds < 0 && errno != EINTR) || !FD_ISSET (fd, &readfds))
exit (1);
exit (0);
}
sleep (1);
if (waitpid (pid, &status, WNOHANG) == 0 || status == 1)
exit (1);
]])],
ccvs_cv_func_select_hang=no,
ccvs_cv_func_select_hang=yes,
dnl When crosscompiling, assume it is broken.
ccvs_cv_func_select_hang=yes)
])
if test $ccvs_cv_func_select_hang = yes; then
ccvs_PREREQ_SELECT
AC_LIBOBJ(select)
AC_DEFINE(select, rpl_select,
[Define to rpl_select if the replacement function should be used.])
fi
])
AC_DEFUN([ccvs_PREREQ_SELECT], [
AC_CHECK_HEADERS(fcntl.h unistd.h)])
select
(application/octet-stream, 273 B) - not displayed
1171830
(application/octet-stream, 6.2 KB) - not displayed