Commit f8ad850f authored by Al Viro's avatar Al Viro
Browse files

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: default avatarAl Viro <viro@zeniv.linux.org.uk>
parent f8d7e187
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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,
+31 −12
Original line number Diff line number Diff line
@@ -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;
}
+5 −0
Original line number Diff line number Diff line
@@ -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));