Re: [PATCH v9] fs: Add VirtualBox guest shared folder (vboxsf) support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Tue, Apr 02, 2019 at 01:54:21PM +0200, Hans de Goede wrote:

> +static int sf_dir_open(struct inode *inode, struct file *file)
> +{
> +	struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb);
> +	struct shfl_createparms params = {};
> +	struct sf_dir_info *sf_d;
> +	int err;
> +
> +	sf_d = vboxsf_dir_info_alloc();
> +	if (!sf_d)
> +		return -ENOMEM;
> +
> +	params.handle = SHFL_HANDLE_NIL;
> +	params.create_flags = 0
> +	    | SHFL_CF_DIRECTORY
> +	    | SHFL_CF_ACT_OPEN_IF_EXISTS
> +	    | SHFL_CF_ACT_FAIL_IF_NEW | SHFL_CF_ACCESS_READ;
> +
> +	err = vboxsf_create_at_dentry(file_dentry(file), &params);
> +	if (err == 0) {
> +		if (params.result == SHFL_FILE_EXISTS) {
> +			err = vboxsf_dir_read_all(sf_g, sf_d, params.handle);
> +			if (!err)
> +				file->private_data = sf_d;
> +		} else
> +			err = -ENOENT;
> +
> +		vboxsf_close(sf_g->root, params.handle);
> +	}
> +
> +	if (err)
> +		vboxsf_dir_info_free(sf_d);
> +
> +	return err;
> +}
> +
> +/**
> + * This is called when reference count of [file] goes to zero. Notify
> + * the host that it can free whatever is associated with this directory
> + * and deallocate our own internal buffers
> + * Return: 0 or negative errno value.
> + * @inode	inode
> + * @file	file
> + */
> +static int sf_dir_release(struct inode *inode, struct file *file)
> +{
> +	if (file->private_data)
> +		vboxsf_dir_info_free(file->private_data);
> +
> +	return 0;
> +}
> +
> +/**
> + * Translate RTFMODE into DT_xxx (in conjunction to rtDirType())
> + * Return: d_type
> + * @mode	file mode
> + */
> +static int sf_get_d_type(u32 mode)
> +{
> +	int d_type;
> +
> +	switch (mode & SHFL_TYPE_MASK) {
> +	case SHFL_TYPE_FIFO:
> +		d_type = DT_FIFO;
> +		break;
> +	case SHFL_TYPE_DEV_CHAR:
> +		d_type = DT_CHR;
> +		break;
> +	case SHFL_TYPE_DIRECTORY:
> +		d_type = DT_DIR;
> +		break;
> +	case SHFL_TYPE_DEV_BLOCK:
> +		d_type = DT_BLK;
> +		break;
> +	case SHFL_TYPE_FILE:
> +		d_type = DT_REG;
> +		break;
> +	case SHFL_TYPE_SYMLINK:
> +		d_type = DT_LNK;
> +		break;
> +	case SHFL_TYPE_SOCKET:
> +		d_type = DT_SOCK;
> +		break;
> +	case SHFL_TYPE_WHITEOUT:
> +		d_type = DT_WHT;
> +		break;
> +	default:
> +		d_type = DT_UNKNOWN;
> +		break;
> +	}
> +	return d_type;
> +}

> +/**
> + * Extract element ([dir]->f_pos) from the directory [dir] into [d_name].
> + * Return: 0 or negative errno value.
> + * @dir		Directory to get element at f_pos from
> + * @d_name	Buffer in which to return element name
> + * @d_type	Buffer in which to return element file-type
> + */
> +static int sf_getdent(struct file *dir, char d_name[NAME_MAX], int *d_type)
> +{
> +	struct sf_glob_info *sf_g = GET_GLOB_INFO(file_inode(dir)->i_sb);
> +	struct sf_dir_info *sf_d = dir->private_data;
> +	struct list_head *pos;
> +	loff_t cur = 0;
> +
> +	list_for_each(pos, &sf_d->info_list) {
> +		struct shfl_dirinfo *info;
> +		struct sf_dir_buf *b;
> +		loff_t i;
> +
> +		b = list_entry(pos, struct sf_dir_buf, head);

	list_for_each_entry(b, &sf_d->info_list, head) { , surely?

> +		if (dir->f_pos >= cur + b->entries) {
> +			cur += b->entries;
> +			continue;
> +		}
> +
> +		for (i = 0, info = b->buf; i < dir->f_pos - cur; ++i) {
> +			size_t size;
> +
> +			size = offsetof(struct shfl_dirinfo, name.string) +
> +			       info->name.size;
> +			info = (struct shfl_dirinfo *)((uintptr_t) info + size);

Umm...  How is alignment handled?


One note here: ->f_pos is not your concern.  Leave it alone, just use
ctx->pos.

> +static int sf_dir_iterate(struct file *dir, struct dir_context *ctx)
> +{
> +	for (;;) {
> +		int err;
> +		ino_t fake_ino;
> +		loff_t sanity;
> +		char d_name[NAME_MAX];
> +		int d_type = DT_UNKNOWN;
> +
> +		err = sf_getdent(dir, d_name, &d_type);
> +		switch (err) {
> +		case 1:
> +			return 0;
> +
> +		case 0:
> +			break;
> +
> +		case -1:
> +		default:
> +			/* skip erroneous entry and proceed */
> +			dir->f_pos += 1;
> +			ctx->pos += 1;
> +			continue;
> +		}

Yecchhh....  Took me a while to figure out that it's
		if (unlikely(err)) {	/* EOF or an error */
			if (err == 1)
				return 0;
			/* skip erroneous entry and proceed */
			ctx->pos += 1;
			continue;
		}

> +
> +		/* d_name now contains a valid entry name */
> +		sanity = ctx->pos + 0xbeef;
> +		fake_ino = sanity;
> +		/*
> +		 * On 32 bit systems pos is 64 signed, while ino is 32 bit
> +		 * unsigned so fake_ino may overflow, check for this.
> +		 */
> +		if (sanity - fake_ino) {
> +			vbg_err("vboxsf: can not compute ino\n");
> +			return -EINVAL;
> +		}
> +		if (!dir_emit(ctx, d_name, strlen(d_name), fake_ino, d_type))
> +			return 0;
> +
> +		dir->f_pos += 1;

Again, leave ->f_pos alone; it's caller's responsibility.

> +		ctx->pos += 1;
> +	}
> +}
> +
> +const struct file_operations vboxsf_dir_fops = {
> +	.open = sf_dir_open,
> +	.iterate = sf_dir_iterate,
> +	.release = sf_dir_release,
> +	.read = generic_read_dir,
> +	.llseek = generic_file_llseek,
> +};

> +static struct dentry *sf_lookup(struct inode *parent, struct dentry *dentry,
> +				unsigned int flags)
> +{
> +	struct shfl_fsobjinfo fsinfo;
> +	struct sf_inode_info *sf_i;
> +	struct sf_glob_info *sf_g;
> +	struct inode *inode;
> +	ino_t ino;
> +	int err;
> +
> +	sf_g = GET_GLOB_INFO(parent->i_sb);
> +	sf_i = GET_INODE_INFO(parent);
> +
> +	err = vboxsf_stat_dentry(dentry, &fsinfo);
> +	if (err) {
> +		if (err != -ENOENT)
> +			return ERR_PTR(err);
> +		/*
> +		 * -ENOENT: add NULL inode to dentry so it later can
> +		 * be created via call to create/mkdir/open
> +		 */
> +		inode = NULL;
> +	} else {
> +		ino = iunique(parent->i_sb, 1);
> +		inode = iget_locked(parent->i_sb, ino);
> +		if (!inode)
> +			return ERR_PTR(-ENOMEM);
> +
> +		vboxsf_init_inode(sf_g, inode, &fsinfo);
> +		unlock_new_inode(inode);
> +	}
> +
> +	dentry->d_time = jiffies;
> +	d_set_d_op(dentry, &sf_dentry_ops);
> +	d_add(dentry, inode);

	No.  Just return d_splice_alias(inode, dentry);
What's more, d_set_d_op() is not needed - just set
sb->s_d_op when setting superblock up.  So I would rather do this:

	dentry->d_time = jiffies;
	err = vboxsf_stat_dentry(dentry, &fsinfo);
	if (err) {
		inode = err == -ENOENT ? NULL : PTR_ERR(inode);
	} else {
		ino = iunique(parent->i_sb, 1);
		inode = iget_locked(parent->i_sb, ino);

		if (inode) {
			vboxsf_init_inode(sf_g, inode, &fsinfo);
			unlock_new_inode(inode);
		} else {
			inode = ERR_PTR(-ENOMEM);
		}
	}
	return d_splice_alias(inode, dentry);

Wait, what?  Why do you bother with picking an unique inumber,
followed by icache lookup, anyway?

> +static int sf_instantiate(struct inode *parent, struct dentry *dentry,
> +			  struct shfl_fsobjinfo *info, u64 handle)
> +{
> +	struct sf_glob_info *sf_g = GET_GLOB_INFO(parent->i_sb);
> +	struct sf_inode_info *sf_i;
> +	struct inode *inode;
> +	ino_t ino;
> +
> +	ino = iunique(parent->i_sb, 1);
> +	inode = iget_locked(parent->i_sb, ino);
> +	if (!inode) {
> +		if (handle != SHFL_HANDLE_NIL)
> +			vboxsf_close(sf_g->root, handle);
> +		return -ENOMEM;
> +	}
> +
> +	sf_i = GET_INODE_INFO(inode);
> +	/* the host may have given us different attr then requested */
> +	sf_i->force_restat = 1;
> +	sf_i->handle = handle;
> +	vboxsf_init_inode(sf_g, inode, info);
> +
> +	d_instantiate(dentry, inode);
> +	unlock_new_inode(inode);

	d_instantiate_new, please...
> +
> +	return 0;
> +}

And your use of iget_locked/iunique (both here and in lookup) is... odd.
You try to pick a unique inumber; check that it *is* unique (by icache
lookups), then you try to find an inode with that that inumber in there,
hopefully still finding none, then you allocate an inode, then you
insert it into the icache.

What the hell is that dance for?  To give them unique inumbers?  You
intend the hash lookup to fail; in fact, if it doesn't you are deep
in trouble.  There are many saner ways to get unique numbers allocated
(idr, for one)...

> +static int sf_unlink_aux(struct inode *parent, struct dentry *dentry,
> +			 int is_dir)
> +{
> +	struct sf_glob_info *sf_g = GET_GLOB_INFO(parent->i_sb);
> +	struct sf_inode_info *sf_parent_i = GET_INODE_INFO(parent);
> +	struct inode *inode = d_inode(dentry);
> +	struct shfl_string *path;
> +	uint32_t flags;
> +	int err;
> +
> +	flags = is_dir ? SHFL_REMOVE_DIR : SHFL_REMOVE_FILE;
> +	if (inode && (inode->i_mode & S_IFLNK) == S_IFLNK)
> +		flags |= SHFL_REMOVE_SYMLINK;

Um...  How can we get here with NULL inode and do we really
want to do anything in that case?

> +	path = vboxsf_path_from_dentry(sf_g, dentry);
> +	if (IS_ERR(path))
> +		return PTR_ERR(path);
> +
> +	err = vboxsf_remove(sf_g->root, path, flags);
> +	__putname(path);
> +	if (err)
> +		return err;
> +
> +	/* parent directory access/change time changed */
> +	sf_parent_i->force_restat = 1;
> +
> +	return 0;
> +}
> +
> +/**
> + * Remove a regular file.
> + * Return: 0 or negative errno value.
> + * @parent	inode of the directory
> + * @dentry	directory cache entry
> + */
> +static int sf_unlink(struct inode *parent, struct dentry *dentry)
> +{
> +	return sf_unlink_aux(parent, dentry, 0);
> +}
> +
> +/**
> + * Remove a directory.
> + * Return: 0 or negative errno value.
> + * @parent	inode of the directory
> + * @dentry	directory cache entry
> + */
> +static int sf_rmdir(struct inode *parent, struct dentry *dentry)
> +{
> +	return sf_unlink_aux(parent, dentry, 1);
> +}
> +
> +/**
> + * Rename a regular file / directory.
> + * Return: 0 or negative errno value.
> + * @old_parent	inode of the old parent directory
> + * @old_dentry	old directory cache entry
> + * @new_parent	inode of the new parent directory
> + * @new_dentry	new directory cache entry
> + * @flags	flags
> + */
> +static int sf_rename(struct inode *old_parent, struct dentry *old_dentry,
> +		     struct inode *new_parent, struct dentry *new_dentry,
> +		     unsigned int flags)
> +{
> +	struct sf_glob_info *sf_g = GET_GLOB_INFO(old_parent->i_sb);
> +	struct sf_inode_info *sf_old_parent_i = GET_INODE_INFO(old_parent);
> +	struct sf_inode_info *sf_new_parent_i = GET_INODE_INFO(new_parent);
> +	u32 shfl_flags = SHFL_RENAME_FILE | SHFL_RENAME_REPLACE_IF_EXISTS;
> +	struct shfl_string *old_path, *new_path;
> +	int err;
> +
> +	if (flags)
> +		return -EINVAL;
> +
> +	if (sf_g != GET_GLOB_INFO(new_parent->i_sb))
> +		return -EINVAL;

Huh?  How can that happen?  We are guaranteed that old_parent->i_sb ==
new_parent->i_sb == old_dentry->d_sb == new_dentry->d_sb.  That check
makes no sense...

> +static ssize_t sf_reg_write(struct file *file, const char __user *buf,
> +			    size_t size, loff_t *off)
> +{
> +	struct inode *inode = file_inode(file);
> +	struct sf_inode_info *sf_i = GET_INODE_INFO(inode);
> +	struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb);
> +	struct sf_reg_info *sf_r = file->private_data;
> +	u32 nwritten;
> +	u64 pos;
> +	int err;
> +
> +	pos = *off;
> +	if (file->f_flags & O_APPEND) {
> +		pos = inode->i_size;
> +		*off = pos;
> +	}

Racy.  lseek() and you are in trouble here.

> +	if (!size)
> +		return 0;
> +
> +	if (size > SHFL_MAX_RW_COUNT)
> +		nwritten = SHFL_MAX_RW_COUNT;
> +	else
> +		nwritten = size;
> +
> +	/* Make sure any pending writes done through mmap are flushed */
> +	err = filemap_fdatawait_range(inode->i_mapping, pos, pos + nwritten);
> +	if (err)
> +		return err;
> +
> +	err = vboxsf_write(sf_g->root, sf_r->handle, pos, &nwritten,
> +			   (uintptr_t)buf, true);

Eww...  "true" being what, "it's a userland address, not a kernel one"?
Almost always a sign of bad interface...

> +	if (err)
> +		return err;
> +
> +	*off += nwritten;
> +	if (*off > inode->i_size)
> +		i_size_write(inode, *off);
> +
> +	/* Invalidate page-cache so that mmap using apps see the changes too */
> +	invalidate_mapping_pages(inode->i_mapping, pos >> PAGE_SHIFT,
> +				 *off >> PAGE_SHIFT);
> +
> +	/* mtime changed */
> +	sf_i->force_restat = 1;
> +	return nwritten;
> +}

> +	/* Already open? */
> +	if (sf_i->handle != SHFL_HANDLE_NIL) {
> +		sf_r->handle = sf_i->handle;
> +		sf_i->handle = SHFL_HANDLE_NIL;
> +		sf_i->file = file;
> +		file->private_data = sf_r;

Obviously racy.

> +	/* the host may have given us different attr then requested */
> +	sf_i->force_restat = 1;
> +	sf_r->handle = params.handle;
> +	sf_i->file = file;

Ditto.  Two opens in parallel and you are in trouble.

> +	file->private_data = sf_r;
> +	return 0;
> +}

> +static int sf_reg_release(struct inode *inode, struct file *file)
> +{
> +	struct sf_reg_info *sf_r;
> +	struct sf_glob_info *sf_g;
> +	struct sf_inode_info *sf_i = GET_INODE_INFO(inode);
> +
> +	sf_g = GET_GLOB_INFO(inode->i_sb);
> +	sf_r = file->private_data;
> +
> +	filemap_write_and_wait(inode->i_mapping);
> +
> +	vboxsf_close(sf_g->root, sf_r->handle);
> +
> +	kfree(sf_r);
> +	sf_i->file = NULL;
> +	sf_i->handle = SHFL_HANDLE_NIL;
> +	file->private_data = NULL;
> +	return 0;
> +}

Also looks confused re multiple overlapping opens...

> +	/**
> +	 * Number of hard links to this filesystem object (st_nlink).
> +	 * This field is 1 if the filesystem doesn't support hardlinking or
> +	 * the information isn't available.
> +	 */
> +	u32 hardlinks;

Wait a sec; what happens if you *do* have hardlinks?  Your lookup will
give each an inode of its own...  Confused...

> +int vboxsf_write(u32 root, u64 handle, u64 offset,
> +		 u32 *buf_len, uintptr_t buf, bool user)
> +{
> +	struct shfl_write parms;
> +	int err;
> +
> +	parms.root.type = VMMDEV_HGCM_PARM_TYPE_32BIT;
> +	parms.root.u.value32 = root;
> +
> +	parms.handle.type = VMMDEV_HGCM_PARM_TYPE_64BIT;
> +	parms.handle.u.value64 = handle;
> +	parms.offset.type = VMMDEV_HGCM_PARM_TYPE_64BIT;
> +	parms.offset.u.value64 = offset;
> +	parms.cb.type = VMMDEV_HGCM_PARM_TYPE_32BIT;
> +	parms.cb.u.value32 = *buf_len;
> +	if (user)
> +		parms.buffer.type = VMMDEV_HGCM_PARM_TYPE_LINADDR_IN;
> +	else
> +		parms.buffer.type = VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_IN;
> +	parms.buffer.u.pointer.size = *buf_len;
> +	parms.buffer.u.pointer.u.linear_addr = buf;
> +
> +	err = vboxsf_call(SHFL_FN_WRITE, &parms, SHFL_CPARMS_WRITE, NULL);
> +
> +	*buf_len = parms.cb.u.value32;
> +	return err;

Egads...  So let me see if I got it straight - your vboxsf_call() is in
the business of going through the process' page tables, etc.?  In
hgcm_call_preprocess_linaddr(), presumably?

Frankly, I would suggest to do an equivalent of that on your own, and
to hell with these kludges - there's no existing users in the kernel
right now and it would be much saner to get rid of the "copy_from_user()
deep in the guts of vboxsf_call()" thing.



[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [Samba]     [Device Mapper]     [CEPH Development]

  Powered by Linux