/** How do I close() a file in a multithreaded program?

Well, the HP-UX manual says:

    [EINTR] An attempt to close a slow device or connection or file with
    pending aio requests was interrupted by a signal. The file descriptor
    still points to an open device or connection or file.

and SunOS/Solaris appears to have the same behavior, so simply retry if we get EINTR, right?

But FreeBSD always destroys the file descriptor before it returns EINTR, so we need the opposite
algorithm there; retrying will destroy other (recently created by other threads) file descriptors.

It gets worse though: A quick check of the Linux kernel source code shows that Linux destroys
the file descriptor before it returns EINTR like FreeBSD, but prior to 2.6.24 on NFS-mounted
filesystems - we don't.

It gets much worse: OSX normally works like FreeBSD, but setting $COMMAND_MODE can load a wrapper
in -lSystem where the library function close() will call pthread_testcancel() which can return EINTR
before the close() system call has fired off.

Now if you only ever create (open/accept/etc) and destroy (close) file descriptors in a single thread,
you can ignore this problem: You're effectively single-threaded as far as this problem is concerned,
but if you don't, closing files is complicated, and that's where this routine comes in:

	1. wrap any routine that creates file descriptors with safeclose_lock() and safeclose_unlock()
	2. call safeclose() instead of close()

*/

#include <pthread.h>
#include <errno.h>

#if defined(__linux__)
#include <linux/magic.h>
#include <sys/utsname.h>
#include <sys/statfs.h>
#endif

#if defined(__MACOSX__) || defined(__linux__)
#include <stdio.h>
#include <stdlib.h>
#endif

static pthread_rwlock_t safeclose_rwlock = PTHREAD_RWLOCK_INITIALIZER;
static int safeclose_emul(int fd)
{
	int result, save_errno;

	pthread_rwlock_wrlock(&safeclose_rwlock);
	do {
		result = close(fd);
	} while (result == -1 && errno == EINTR);
	save_errno = errno;
	pthread_rdlock_unlock(&safeclose_rwlock);
	errno = save_errno;
	return result;
}
void safeclose_lock(void)
{
	pthread_rwlock_rdlock(&safeclose_rwlock);
}
void safeclose_unlock(void)
{
	pthread_rdlock_unlock(&safeclose_rwlock);
}

int safeclose(int fd)
{
	int result;
#if defined(__MACOSX__)
	/* The OSX close() system call works by always closing the fd first,
	   however if you use -lSystem then close() is wrapped with something
	   that calls __pthread_testcancel() first based on the $COMMAND_MODE
	   environment variable. Since __pthread_testcancel() can return EINTR,
	   that means OSX _sometimes_ needs a retry, and sometimes does not.
	*/
	if(!getenv("COMMAND_MODE")) {
		result = close(fd);
		if(result == -1 && errno == EINTR) result = 0;
		return result;
	}

	return safeclose_emul(fd);
	
#elif defined(__linux__)
	/* Linux usually destroys the fd before close() returns EINTR:

		http://lxr.free-electrons.com/source/fs/open.c#L1006

	however if you are using NFS on 2.6.24 or earlier, EINTR can leak fds!
	*/
	static int safe = -1;
	if(safe == -1) {
		struct utsname u;
		int a, b, c;

		if(__builtin_expect((uname(&u) == -1),0)) abort();
		if(sscanf(u.release, "%d.%d.%d", &a,&b,&c) == 3
		&& (a < 2 || (a == 2 && b < 6) || (a == 2 && b == 6 && c < 24)))
			safe = 0;
		else
			safe = 1;
	}

	if(!safe) {
		struct statfs fsb;
		if(fstatfs(fd, &fsb) == -1) abort();
		if(fsb.f_type == NFS_SUPER_MAGIC) 
			return safeclose_emul(fd);
	}

	result = close(fd);
	if(result == -1 && errno == EINTR) result = 0;
	return result;

#elif defined(BSD)
	/* FreeBSD quite simply destroys the fd before close() returns EINTR:

		http://fxr.watson.org/fxr/source/kern/kern_descrip.c#L1182
	*/
	result = close(fd);
	if(result == -1 && errno == EINTR) result = 0;
	return result;

#elif defined(HPUX) || defined(__sun)
	/* HP-UX manual says:

    [EINTR] An attempt to close a slow device or connection or file with
    pending aio requests was interrupted by a signal. The file descriptor
    still points to an open device or connection or file.

	SunOS/Solaris appears to have the same behavior.

	*/
	do {
		result = close(fd);
	} while(result == -1 && errno == EINTR);
	return result;
#else
	return safeclose_emul(fd);
#endif
}
/*
-- Geo Carncross <geocar@sdf.org>
*/
