try to get rid of races in hostfs open()

In case of mode mismatch, do *not* blindly close the descriptor
another openers might be using right now.  Open the underlying
file with currently sufficient mode, then
	* if current mode has grown so that it's sufficient for
us now, just close our new fd
	* if current mode has grown and our fd is *not* enough
to cover it, close and repeat.
	* otherwise, install our fd if the file hadn't been
opened at all or dup2() our fd over the current one (and close
our fd).
Critical section is protected by mutex; yes, system-wide.  All
we do under it is a bunch of comparison and maybe an overwriting
dup2() on host.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
This commit is contained in:
Al Viro 2010-06-06 23:49:18 -04:00
parent f8d7e1877e
commit f8ad850f11
3 changed files with 37 additions and 12 deletions

View file

@ -74,6 +74,7 @@ extern void *open_dir(char *path, int *err_out);
extern char *read_dir(void *stream, unsigned long long *pos,
unsigned long long *ino_out, int *len_out);
extern void close_file(void *stream);
extern int replace_file(int oldfd, int fd);
extern void close_dir(void *stream);
extern int read_file(int fd, unsigned long long *offset, char *buf, int len);
extern int write_file(int fd, unsigned long long *offset, const char *buf,

View file

@ -302,27 +302,22 @@ int hostfs_readdir(struct file *file, void *ent, filldir_t filldir)
int hostfs_file_open(struct inode *ino, struct file *file)
{
static DEFINE_MUTEX(open_mutex);
char *name;
fmode_t mode = 0;
int err;
int r = 0, w = 0, fd;
mode = file->f_mode & (FMODE_READ | FMODE_WRITE);
if ((mode & HOSTFS_I(ino)->mode) == mode)
return 0;
/*
* The file may already have been opened, but with the wrong access,
* so this resets things and reopens the file with the new access.
*/
if (HOSTFS_I(ino)->fd != -1) {
close_file(&HOSTFS_I(ino)->fd);
HOSTFS_I(ino)->fd = -1;
}
mode |= HOSTFS_I(ino)->mode;
HOSTFS_I(ino)->mode |= mode;
if (HOSTFS_I(ino)->mode & FMODE_READ)
retry:
if (mode & FMODE_READ)
r = 1;
if (HOSTFS_I(ino)->mode & FMODE_WRITE)
if (mode & FMODE_WRITE)
w = 1;
if (w)
r = 1;
@ -335,7 +330,31 @@ int hostfs_file_open(struct inode *ino, struct file *file)
__putname(name);
if (fd < 0)
return fd;
FILE_HOSTFS_I(file)->fd = fd;
mutex_lock(&open_mutex);
/* somebody else had handled it first? */
if ((mode & HOSTFS_I(ino)->mode) == mode) {
mutex_unlock(&open_mutex);
return 0;
}
if ((mode | HOSTFS_I(ino)->mode) != mode) {
mode |= HOSTFS_I(ino)->mode;
mutex_unlock(&open_mutex);
close_file(&fd);
goto retry;
}
if (HOSTFS_I(ino)->fd == -1) {
HOSTFS_I(ino)->fd = fd;
} else {
err = replace_file(fd, HOSTFS_I(ino)->fd);
close_file(&fd);
if (err < 0) {
mutex_unlock(&open_mutex);
return err;
}
}
HOSTFS_I(ino)->mode = mode;
mutex_unlock(&open_mutex);
return 0;
}

View file

@ -160,6 +160,11 @@ int fsync_file(int fd, int datasync)
return 0;
}
int replace_file(int oldfd, int fd)
{
return dup2(oldfd, fd);
}
void close_file(void *stream)
{
close(*((int *) stream));