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