Re: close return value (was Re: [ANNOUNCE] Ext3 vs Reiserfs benchmarks)

Elladan (elladan@eskimo.com)
Wed, 17 Jul 2002 09:49:33 -0700


On Wed, Jul 17, 2002 at 04:39:28PM +0200, Andreas Schwab wrote:
> Elladan <elladan@eskimo.com> writes:
>
> |> On Wed, Jul 17, 2002 at 12:17:40AM -0400, Stevie O wrote:
> |> > At 07:22 PM 7/16/2002 -0700, Elladan wrote:
> |> > > 1. Thread 1 performs close() on a file descriptor. close fails.
> |> > > 2. Thread 2 performs open().
> |> > >* 3. Thread 1 performs close() again, just to make sure.
> |> > >
> |> > >
> |> > >open() may return any file descriptor not currently in use.
> |> >
> |> > I'm confused here... the only way close() can fail is if the file
> |> > descriptor is invalid (EBADF); wouldn't it be rather stupid to close()
> |> > a known-to-be-bad descriptor?
> |>
> |> Well, obviously, if that's the case. However, the man page for close(2)
> |> doesn't agree (see below). close() is allowed to return EBADF, EINTR,
> |> or EIO.
> |>
> |> The question is, does the OS standard guarantee that the fd is closed,
> |> even if close() returns EINTR or EIO? Just going by the normal usage of
> |> EINTR, one might think otherwise. It doesn't appear to be documented
> |> one way or another.
>
> POSIX says the state of the file descriptor when close fails (with errno
> != EBADF) is unspecified, which means:
>
> The value or behavior may vary among implementations that conform to
> IEEE Std 1003.1-2001. An application should not rely on the existence
> or validity of the value or behavior. An application that relies on
> any particular value or behavior cannot be assured to be portable
> across conforming implementations.

This doesn't mean an OS shouldn't specify the behavior. Just because
the cross-platform standard leaves it unspecified doesn't mean the OS
should.

Consider what this says, if a particular OS doesn't pick a standard
which the application can port to. It means that the *only way* to
correctly close a file descriptor is like this:

int ret;
do {
ret = close(fd);
} while(ret == -1 && errno != EBADF);

That means, if we get an error, we have to loop until the kernel throws
a BADF error! We can't detect that the file is closed from any other
error value, because only BADF has a defined behavior.

This would sort of work, though of course be hideous, for a single
threaded app. Now consider a multithreaded app. To correctly implement
this we have to lock around all calls to close and
open/socket/dup/pipe/creat/etc...

This is clearly ridiculous, and not at all as intended. Either standard
will work for an OS (though guaranteeing close the first time is much
simpler all around), but it needs to be specified and stuck to, or you
get horrible things like this to work around a bad spec:

void lock_syscalls();
void unlock_syscalls();

int threadsafe_open(const char *file, int flags, mode_t mode)
{
int fd;
lock_syscalls();
fd = open(file, flags, mode);
unlock_syscalls();
return fd;
}

int threadsafe_close(int fd)
{
int ret;
lock_syscalls();
do {
ret = close(fd);
} while(ret == -1 && errno != EBADF);
unlock_syscalls();
return ret;
}

int threadsafe_socket() ...
int threadsafe_pipe() ...
int threadsafe_dup() ...
int threadsafe_creat() ...
int threadsafe_socketpair() ...
int threadsafe_accept() ...

-J

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/