On 03/14/2018 10:52 AM, Bruno Haible wrote:
This is an interesting, creative idea. However, because few people use this
'dup2 as a kind of atomic close' workaround, I fear that it might lead us
into the proximity of OS bugs. Hardly anyone has tested how dup2 works
in the presence of signals and ttys, in the past.

For what it's worth, in Emacs I wrote the attached code (in src/sysdep.c) and it seems to have worked well enough for Emacs so far. Perhaps something like this should be Gnulibified, and the rest of Gnulib should call posix_close.

/* Approximate posix_close and POSIX_CLOSE_RESTART well enough for Emacs.
   For the background behind this mess, please see Austin Group defect 529
   <http://austingroupbugs.net/view.php?id=529>.  */

#ifndef POSIX_CLOSE_RESTART
# define POSIX_CLOSE_RESTART 1
static int
posix_close (int fd, int flag)
{
  /* Only the POSIX_CLOSE_RESTART case is emulated.  */
  eassert (flag == POSIX_CLOSE_RESTART);

  /* Things are tricky if close (fd) returns -1 with errno == EINTR
     on a system that does not define POSIX_CLOSE_RESTART.

     In this case, in some systems (e.g., GNU/Linux, AIX) FD is
     closed, and retrying the close could inadvertently close a file
     descriptor allocated by some other thread.  In other systems
     (e.g., HP/UX) FD is not closed.  And in still other systems
     (e.g., macOS, Solaris), maybe FD is closed, maybe not, and in a
     multithreaded program there can be no way to tell.

     So, in this case, pretend that the close succeeded.  This works
     well on systems like GNU/Linux that close FD.  Although it may
     leak a file descriptor on other systems, the leak is unlikely and
     it's better to leak than to close a random victim.  */
  return close (fd) == 0 || errno == EINTR ? 0 : -1;
}
#endif

/* Close FD, retrying if interrupted.  If successful, return 0;
   otherwise, return -1 and set errno to a non-EINTR value.  Consider
   an EINPROGRESS error to be successful, as that's merely a signal
   arriving.  FD is always closed when this function returns, even
   when it returns -1.

   Do not call this function if FD is nonnegative and might already be closed,
   as that might close an innocent victim opened by some other thread.  */

int
emacs_close (int fd)
{
  while (1)
    {
      int r = posix_close (fd, POSIX_CLOSE_RESTART);
      if (r == 0)
	return r;
      if (!POSIX_CLOSE_RESTART || errno != EINTR)
	{
	  eassert (errno != EBADF || fd < 0);
	  return errno == EINPROGRESS ? 0 : r;
	}
    }
}

Reply via email to