mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-03 18:20:25 +02:00 
			
		
		
		
	vboxsf_init_inode() is used both for initial setup of inode and for metadata updates. Tell it whether we are updating a live inode or setting up a new instance and have it refuse to change type in the former case. [fixed the braino caught by Hans de Goede <hdegoede@redhat.com>] Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
		
			
				
	
	
		
			570 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			570 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// SPDX-License-Identifier: MIT
 | 
						|
/*
 | 
						|
 * VirtualBox Guest Shared Folders support: Utility functions.
 | 
						|
 * Mainly conversion from/to VirtualBox/Linux data structures.
 | 
						|
 *
 | 
						|
 * Copyright (C) 2006-2018 Oracle Corporation
 | 
						|
 */
 | 
						|
 | 
						|
#include <linux/namei.h>
 | 
						|
#include <linux/nls.h>
 | 
						|
#include <linux/sizes.h>
 | 
						|
#include <linux/vfs.h>
 | 
						|
#include "vfsmod.h"
 | 
						|
 | 
						|
struct inode *vboxsf_new_inode(struct super_block *sb)
 | 
						|
{
 | 
						|
	struct vboxsf_sbi *sbi = VBOXSF_SBI(sb);
 | 
						|
	struct inode *inode;
 | 
						|
	unsigned long flags;
 | 
						|
	int cursor, ret;
 | 
						|
	u32 gen;
 | 
						|
 | 
						|
	inode = new_inode(sb);
 | 
						|
	if (!inode)
 | 
						|
		return ERR_PTR(-ENOMEM);
 | 
						|
 | 
						|
	idr_preload(GFP_KERNEL);
 | 
						|
	spin_lock_irqsave(&sbi->ino_idr_lock, flags);
 | 
						|
	cursor = idr_get_cursor(&sbi->ino_idr);
 | 
						|
	ret = idr_alloc_cyclic(&sbi->ino_idr, inode, 1, 0, GFP_ATOMIC);
 | 
						|
	if (ret >= 0 && ret < cursor)
 | 
						|
		sbi->next_generation++;
 | 
						|
	gen = sbi->next_generation;
 | 
						|
	spin_unlock_irqrestore(&sbi->ino_idr_lock, flags);
 | 
						|
	idr_preload_end();
 | 
						|
 | 
						|
	if (ret < 0) {
 | 
						|
		iput(inode);
 | 
						|
		return ERR_PTR(ret);
 | 
						|
	}
 | 
						|
 | 
						|
	inode->i_ino = ret;
 | 
						|
	inode->i_generation = gen;
 | 
						|
	return inode;
 | 
						|
}
 | 
						|
 | 
						|
/* set [inode] attributes based on [info], uid/gid based on [sbi] */
 | 
						|
int vboxsf_init_inode(struct vboxsf_sbi *sbi, struct inode *inode,
 | 
						|
		       const struct shfl_fsobjinfo *info, bool reinit)
 | 
						|
{
 | 
						|
	const struct shfl_fsobjattr *attr;
 | 
						|
	s64 allocated;
 | 
						|
	umode_t mode;
 | 
						|
 | 
						|
	attr = &info->attr;
 | 
						|
 | 
						|
#define mode_set(r) ((attr->mode & (SHFL_UNIX_##r)) ? (S_##r) : 0)
 | 
						|
 | 
						|
	mode = mode_set(IRUSR);
 | 
						|
	mode |= mode_set(IWUSR);
 | 
						|
	mode |= mode_set(IXUSR);
 | 
						|
 | 
						|
	mode |= mode_set(IRGRP);
 | 
						|
	mode |= mode_set(IWGRP);
 | 
						|
	mode |= mode_set(IXGRP);
 | 
						|
 | 
						|
	mode |= mode_set(IROTH);
 | 
						|
	mode |= mode_set(IWOTH);
 | 
						|
	mode |= mode_set(IXOTH);
 | 
						|
 | 
						|
#undef mode_set
 | 
						|
 | 
						|
	/* We use the host-side values for these */
 | 
						|
	inode->i_flags |= S_NOATIME | S_NOCMTIME;
 | 
						|
	inode->i_mapping->a_ops = &vboxsf_reg_aops;
 | 
						|
 | 
						|
	if (SHFL_IS_DIRECTORY(attr->mode)) {
 | 
						|
		if (sbi->o.dmode_set)
 | 
						|
			mode = sbi->o.dmode;
 | 
						|
		mode &= ~sbi->o.dmask;
 | 
						|
		mode |= S_IFDIR;
 | 
						|
		if (!reinit) {
 | 
						|
			inode->i_op = &vboxsf_dir_iops;
 | 
						|
			inode->i_fop = &vboxsf_dir_fops;
 | 
						|
			/*
 | 
						|
			 * XXX: this probably should be set to the number of entries
 | 
						|
			 * in the directory plus two (. ..)
 | 
						|
			 */
 | 
						|
			set_nlink(inode, 1);
 | 
						|
		} else if (!S_ISDIR(inode->i_mode))
 | 
						|
			return -ESTALE;
 | 
						|
		inode->i_mode = mode;
 | 
						|
	} else if (SHFL_IS_SYMLINK(attr->mode)) {
 | 
						|
		if (sbi->o.fmode_set)
 | 
						|
			mode = sbi->o.fmode;
 | 
						|
		mode &= ~sbi->o.fmask;
 | 
						|
		mode |= S_IFLNK;
 | 
						|
		if (!reinit) {
 | 
						|
			inode->i_op = &vboxsf_lnk_iops;
 | 
						|
			set_nlink(inode, 1);
 | 
						|
		} else if (!S_ISLNK(inode->i_mode))
 | 
						|
			return -ESTALE;
 | 
						|
		inode->i_mode = mode;
 | 
						|
	} else {
 | 
						|
		if (sbi->o.fmode_set)
 | 
						|
			mode = sbi->o.fmode;
 | 
						|
		mode &= ~sbi->o.fmask;
 | 
						|
		mode |= S_IFREG;
 | 
						|
		if (!reinit) {
 | 
						|
			inode->i_op = &vboxsf_reg_iops;
 | 
						|
			inode->i_fop = &vboxsf_reg_fops;
 | 
						|
			set_nlink(inode, 1);
 | 
						|
		} else if (!S_ISREG(inode->i_mode))
 | 
						|
			return -ESTALE;
 | 
						|
		inode->i_mode = mode;
 | 
						|
	}
 | 
						|
 | 
						|
	inode->i_uid = sbi->o.uid;
 | 
						|
	inode->i_gid = sbi->o.gid;
 | 
						|
 | 
						|
	inode->i_size = info->size;
 | 
						|
	inode->i_blkbits = 12;
 | 
						|
	/* i_blocks always in units of 512 bytes! */
 | 
						|
	allocated = info->allocated + 511;
 | 
						|
	do_div(allocated, 512);
 | 
						|
	inode->i_blocks = allocated;
 | 
						|
 | 
						|
	inode->i_atime = ns_to_timespec64(
 | 
						|
				 info->access_time.ns_relative_to_unix_epoch);
 | 
						|
	inode->i_ctime = ns_to_timespec64(
 | 
						|
				 info->change_time.ns_relative_to_unix_epoch);
 | 
						|
	inode->i_mtime = ns_to_timespec64(
 | 
						|
			   info->modification_time.ns_relative_to_unix_epoch);
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
int vboxsf_create_at_dentry(struct dentry *dentry,
 | 
						|
			    struct shfl_createparms *params)
 | 
						|
{
 | 
						|
	struct vboxsf_sbi *sbi = VBOXSF_SBI(dentry->d_sb);
 | 
						|
	struct shfl_string *path;
 | 
						|
	int err;
 | 
						|
 | 
						|
	path = vboxsf_path_from_dentry(sbi, dentry);
 | 
						|
	if (IS_ERR(path))
 | 
						|
		return PTR_ERR(path);
 | 
						|
 | 
						|
	err = vboxsf_create(sbi->root, path, params);
 | 
						|
	__putname(path);
 | 
						|
 | 
						|
	return err;
 | 
						|
}
 | 
						|
 | 
						|
int vboxsf_stat(struct vboxsf_sbi *sbi, struct shfl_string *path,
 | 
						|
		struct shfl_fsobjinfo *info)
 | 
						|
{
 | 
						|
	struct shfl_createparms params = {};
 | 
						|
	int err;
 | 
						|
 | 
						|
	params.handle = SHFL_HANDLE_NIL;
 | 
						|
	params.create_flags = SHFL_CF_LOOKUP | SHFL_CF_ACT_FAIL_IF_NEW;
 | 
						|
 | 
						|
	err = vboxsf_create(sbi->root, path, ¶ms);
 | 
						|
	if (err)
 | 
						|
		return err;
 | 
						|
 | 
						|
	if (params.result != SHFL_FILE_EXISTS)
 | 
						|
		return -ENOENT;
 | 
						|
 | 
						|
	if (info)
 | 
						|
		*info = params.info;
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
int vboxsf_stat_dentry(struct dentry *dentry, struct shfl_fsobjinfo *info)
 | 
						|
{
 | 
						|
	struct vboxsf_sbi *sbi = VBOXSF_SBI(dentry->d_sb);
 | 
						|
	struct shfl_string *path;
 | 
						|
	int err;
 | 
						|
 | 
						|
	path = vboxsf_path_from_dentry(sbi, dentry);
 | 
						|
	if (IS_ERR(path))
 | 
						|
		return PTR_ERR(path);
 | 
						|
 | 
						|
	err = vboxsf_stat(sbi, path, info);
 | 
						|
	__putname(path);
 | 
						|
	return err;
 | 
						|
}
 | 
						|
 | 
						|
int vboxsf_inode_revalidate(struct dentry *dentry)
 | 
						|
{
 | 
						|
	struct vboxsf_sbi *sbi;
 | 
						|
	struct vboxsf_inode *sf_i;
 | 
						|
	struct shfl_fsobjinfo info;
 | 
						|
	struct timespec64 prev_mtime;
 | 
						|
	struct inode *inode;
 | 
						|
	int err;
 | 
						|
 | 
						|
	if (!dentry || !d_really_is_positive(dentry))
 | 
						|
		return -EINVAL;
 | 
						|
 | 
						|
	inode = d_inode(dentry);
 | 
						|
	prev_mtime = inode->i_mtime;
 | 
						|
	sf_i = VBOXSF_I(inode);
 | 
						|
	sbi = VBOXSF_SBI(dentry->d_sb);
 | 
						|
	if (!sf_i->force_restat) {
 | 
						|
		if (time_before(jiffies, dentry->d_time + sbi->o.ttl))
 | 
						|
			return 0;
 | 
						|
	}
 | 
						|
 | 
						|
	err = vboxsf_stat_dentry(dentry, &info);
 | 
						|
	if (err)
 | 
						|
		return err;
 | 
						|
 | 
						|
	dentry->d_time = jiffies;
 | 
						|
	sf_i->force_restat = 0;
 | 
						|
	err = vboxsf_init_inode(sbi, inode, &info, true);
 | 
						|
	if (err)
 | 
						|
		return err;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * If the file was changed on the host side we need to invalidate the
 | 
						|
	 * page-cache for it.  Note this also gets triggered by our own writes,
 | 
						|
	 * this is unavoidable.
 | 
						|
	 */
 | 
						|
	if (timespec64_compare(&inode->i_mtime, &prev_mtime) > 0)
 | 
						|
		invalidate_inode_pages2(inode->i_mapping);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
int vboxsf_getattr(struct user_namespace *mnt_userns, const struct path *path,
 | 
						|
		   struct kstat *kstat, u32 request_mask, unsigned int flags)
 | 
						|
{
 | 
						|
	int err;
 | 
						|
	struct dentry *dentry = path->dentry;
 | 
						|
	struct inode *inode = d_inode(dentry);
 | 
						|
	struct vboxsf_inode *sf_i = VBOXSF_I(inode);
 | 
						|
 | 
						|
	switch (flags & AT_STATX_SYNC_TYPE) {
 | 
						|
	case AT_STATX_DONT_SYNC:
 | 
						|
		err = 0;
 | 
						|
		break;
 | 
						|
	case AT_STATX_FORCE_SYNC:
 | 
						|
		sf_i->force_restat = 1;
 | 
						|
		fallthrough;
 | 
						|
	default:
 | 
						|
		err = vboxsf_inode_revalidate(dentry);
 | 
						|
	}
 | 
						|
	if (err)
 | 
						|
		return err;
 | 
						|
 | 
						|
	generic_fillattr(&init_user_ns, d_inode(dentry), kstat);
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
int vboxsf_setattr(struct user_namespace *mnt_userns, struct dentry *dentry,
 | 
						|
		   struct iattr *iattr)
 | 
						|
{
 | 
						|
	struct vboxsf_inode *sf_i = VBOXSF_I(d_inode(dentry));
 | 
						|
	struct vboxsf_sbi *sbi = VBOXSF_SBI(dentry->d_sb);
 | 
						|
	struct shfl_createparms params = {};
 | 
						|
	struct shfl_fsobjinfo info = {};
 | 
						|
	u32 buf_len;
 | 
						|
	int err;
 | 
						|
 | 
						|
	params.handle = SHFL_HANDLE_NIL;
 | 
						|
	params.create_flags = SHFL_CF_ACT_OPEN_IF_EXISTS |
 | 
						|
			      SHFL_CF_ACT_FAIL_IF_NEW |
 | 
						|
			      SHFL_CF_ACCESS_ATTR_WRITE;
 | 
						|
 | 
						|
	/* this is at least required for Posix hosts */
 | 
						|
	if (iattr->ia_valid & ATTR_SIZE)
 | 
						|
		params.create_flags |= SHFL_CF_ACCESS_WRITE;
 | 
						|
 | 
						|
	err = vboxsf_create_at_dentry(dentry, ¶ms);
 | 
						|
	if (err || params.result != SHFL_FILE_EXISTS)
 | 
						|
		return err ? err : -ENOENT;
 | 
						|
 | 
						|
#define mode_set(r) ((iattr->ia_mode & (S_##r)) ? SHFL_UNIX_##r : 0)
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Setting the file size and setting the other attributes has to
 | 
						|
	 * be handled separately.
 | 
						|
	 */
 | 
						|
	if (iattr->ia_valid & (ATTR_MODE | ATTR_ATIME | ATTR_MTIME)) {
 | 
						|
		if (iattr->ia_valid & ATTR_MODE) {
 | 
						|
			info.attr.mode = mode_set(IRUSR);
 | 
						|
			info.attr.mode |= mode_set(IWUSR);
 | 
						|
			info.attr.mode |= mode_set(IXUSR);
 | 
						|
			info.attr.mode |= mode_set(IRGRP);
 | 
						|
			info.attr.mode |= mode_set(IWGRP);
 | 
						|
			info.attr.mode |= mode_set(IXGRP);
 | 
						|
			info.attr.mode |= mode_set(IROTH);
 | 
						|
			info.attr.mode |= mode_set(IWOTH);
 | 
						|
			info.attr.mode |= mode_set(IXOTH);
 | 
						|
 | 
						|
			if (iattr->ia_mode & S_IFDIR)
 | 
						|
				info.attr.mode |= SHFL_TYPE_DIRECTORY;
 | 
						|
			else
 | 
						|
				info.attr.mode |= SHFL_TYPE_FILE;
 | 
						|
		}
 | 
						|
 | 
						|
		if (iattr->ia_valid & ATTR_ATIME)
 | 
						|
			info.access_time.ns_relative_to_unix_epoch =
 | 
						|
					    timespec64_to_ns(&iattr->ia_atime);
 | 
						|
 | 
						|
		if (iattr->ia_valid & ATTR_MTIME)
 | 
						|
			info.modification_time.ns_relative_to_unix_epoch =
 | 
						|
					    timespec64_to_ns(&iattr->ia_mtime);
 | 
						|
 | 
						|
		/*
 | 
						|
		 * Ignore ctime (inode change time) as it can't be set
 | 
						|
		 * from userland anyway.
 | 
						|
		 */
 | 
						|
 | 
						|
		buf_len = sizeof(info);
 | 
						|
		err = vboxsf_fsinfo(sbi->root, params.handle,
 | 
						|
				    SHFL_INFO_SET | SHFL_INFO_FILE, &buf_len,
 | 
						|
				    &info);
 | 
						|
		if (err) {
 | 
						|
			vboxsf_close(sbi->root, params.handle);
 | 
						|
			return err;
 | 
						|
		}
 | 
						|
 | 
						|
		/* the host may have given us different attr then requested */
 | 
						|
		sf_i->force_restat = 1;
 | 
						|
	}
 | 
						|
 | 
						|
#undef mode_set
 | 
						|
 | 
						|
	if (iattr->ia_valid & ATTR_SIZE) {
 | 
						|
		memset(&info, 0, sizeof(info));
 | 
						|
		info.size = iattr->ia_size;
 | 
						|
		buf_len = sizeof(info);
 | 
						|
		err = vboxsf_fsinfo(sbi->root, params.handle,
 | 
						|
				    SHFL_INFO_SET | SHFL_INFO_SIZE, &buf_len,
 | 
						|
				    &info);
 | 
						|
		if (err) {
 | 
						|
			vboxsf_close(sbi->root, params.handle);
 | 
						|
			return err;
 | 
						|
		}
 | 
						|
 | 
						|
		/* the host may have given us different attr then requested */
 | 
						|
		sf_i->force_restat = 1;
 | 
						|
	}
 | 
						|
 | 
						|
	vboxsf_close(sbi->root, params.handle);
 | 
						|
 | 
						|
	/* Update the inode with what the host has actually given us. */
 | 
						|
	if (sf_i->force_restat)
 | 
						|
		vboxsf_inode_revalidate(dentry);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * [dentry] contains string encoded in coding system that corresponds
 | 
						|
 * to [sbi]->nls, we must convert it to UTF8 here.
 | 
						|
 * Returns a shfl_string allocated through __getname (must be freed using
 | 
						|
 * __putname), or an ERR_PTR on error.
 | 
						|
 */
 | 
						|
struct shfl_string *vboxsf_path_from_dentry(struct vboxsf_sbi *sbi,
 | 
						|
					    struct dentry *dentry)
 | 
						|
{
 | 
						|
	struct shfl_string *shfl_path;
 | 
						|
	int path_len, out_len, nb;
 | 
						|
	char *buf, *path;
 | 
						|
	wchar_t uni;
 | 
						|
	u8 *out;
 | 
						|
 | 
						|
	buf = __getname();
 | 
						|
	if (!buf)
 | 
						|
		return ERR_PTR(-ENOMEM);
 | 
						|
 | 
						|
	path = dentry_path_raw(dentry, buf, PATH_MAX);
 | 
						|
	if (IS_ERR(path)) {
 | 
						|
		__putname(buf);
 | 
						|
		return ERR_CAST(path);
 | 
						|
	}
 | 
						|
	path_len = strlen(path);
 | 
						|
 | 
						|
	if (sbi->nls) {
 | 
						|
		shfl_path = __getname();
 | 
						|
		if (!shfl_path) {
 | 
						|
			__putname(buf);
 | 
						|
			return ERR_PTR(-ENOMEM);
 | 
						|
		}
 | 
						|
 | 
						|
		out = shfl_path->string.utf8;
 | 
						|
		out_len = PATH_MAX - SHFLSTRING_HEADER_SIZE - 1;
 | 
						|
 | 
						|
		while (path_len) {
 | 
						|
			nb = sbi->nls->char2uni(path, path_len, &uni);
 | 
						|
			if (nb < 0) {
 | 
						|
				__putname(shfl_path);
 | 
						|
				__putname(buf);
 | 
						|
				return ERR_PTR(-EINVAL);
 | 
						|
			}
 | 
						|
			path += nb;
 | 
						|
			path_len -= nb;
 | 
						|
 | 
						|
			nb = utf32_to_utf8(uni, out, out_len);
 | 
						|
			if (nb < 0) {
 | 
						|
				__putname(shfl_path);
 | 
						|
				__putname(buf);
 | 
						|
				return ERR_PTR(-ENAMETOOLONG);
 | 
						|
			}
 | 
						|
			out += nb;
 | 
						|
			out_len -= nb;
 | 
						|
		}
 | 
						|
		*out = 0;
 | 
						|
		shfl_path->length = out - shfl_path->string.utf8;
 | 
						|
		shfl_path->size = shfl_path->length + 1;
 | 
						|
		__putname(buf);
 | 
						|
	} else {
 | 
						|
		if ((SHFLSTRING_HEADER_SIZE + path_len + 1) > PATH_MAX) {
 | 
						|
			__putname(buf);
 | 
						|
			return ERR_PTR(-ENAMETOOLONG);
 | 
						|
		}
 | 
						|
		/*
 | 
						|
		 * dentry_path stores the name at the end of buf, but the
 | 
						|
		 * shfl_string string we return must be properly aligned.
 | 
						|
		 */
 | 
						|
		shfl_path = (struct shfl_string *)buf;
 | 
						|
		memmove(shfl_path->string.utf8, path, path_len);
 | 
						|
		shfl_path->string.utf8[path_len] = 0;
 | 
						|
		shfl_path->length = path_len;
 | 
						|
		shfl_path->size = path_len + 1;
 | 
						|
	}
 | 
						|
 | 
						|
	return shfl_path;
 | 
						|
}
 | 
						|
 | 
						|
int vboxsf_nlscpy(struct vboxsf_sbi *sbi, char *name, size_t name_bound_len,
 | 
						|
		  const unsigned char *utf8_name, size_t utf8_len)
 | 
						|
{
 | 
						|
	const char *in;
 | 
						|
	char *out;
 | 
						|
	size_t out_len;
 | 
						|
	size_t out_bound_len;
 | 
						|
	size_t in_bound_len;
 | 
						|
 | 
						|
	in = utf8_name;
 | 
						|
	in_bound_len = utf8_len;
 | 
						|
 | 
						|
	out = name;
 | 
						|
	out_len = 0;
 | 
						|
	/* Reserve space for terminating 0 */
 | 
						|
	out_bound_len = name_bound_len - 1;
 | 
						|
 | 
						|
	while (in_bound_len) {
 | 
						|
		int nb;
 | 
						|
		unicode_t uni;
 | 
						|
 | 
						|
		nb = utf8_to_utf32(in, in_bound_len, &uni);
 | 
						|
		if (nb < 0)
 | 
						|
			return -EINVAL;
 | 
						|
 | 
						|
		in += nb;
 | 
						|
		in_bound_len -= nb;
 | 
						|
 | 
						|
		nb = sbi->nls->uni2char(uni, out, out_bound_len);
 | 
						|
		if (nb < 0)
 | 
						|
			return nb;
 | 
						|
 | 
						|
		out += nb;
 | 
						|
		out_bound_len -= nb;
 | 
						|
		out_len += nb;
 | 
						|
	}
 | 
						|
 | 
						|
	*out = 0;
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static struct vboxsf_dir_buf *vboxsf_dir_buf_alloc(struct list_head *list)
 | 
						|
{
 | 
						|
	struct vboxsf_dir_buf *b;
 | 
						|
 | 
						|
	b = kmalloc(sizeof(*b), GFP_KERNEL);
 | 
						|
	if (!b)
 | 
						|
		return NULL;
 | 
						|
 | 
						|
	b->buf = kmalloc(DIR_BUFFER_SIZE, GFP_KERNEL);
 | 
						|
	if (!b->buf) {
 | 
						|
		kfree(b);
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	b->entries = 0;
 | 
						|
	b->used = 0;
 | 
						|
	b->free = DIR_BUFFER_SIZE;
 | 
						|
	list_add(&b->head, list);
 | 
						|
 | 
						|
	return b;
 | 
						|
}
 | 
						|
 | 
						|
static void vboxsf_dir_buf_free(struct vboxsf_dir_buf *b)
 | 
						|
{
 | 
						|
	list_del(&b->head);
 | 
						|
	kfree(b->buf);
 | 
						|
	kfree(b);
 | 
						|
}
 | 
						|
 | 
						|
struct vboxsf_dir_info *vboxsf_dir_info_alloc(void)
 | 
						|
{
 | 
						|
	struct vboxsf_dir_info *p;
 | 
						|
 | 
						|
	p = kmalloc(sizeof(*p), GFP_KERNEL);
 | 
						|
	if (!p)
 | 
						|
		return NULL;
 | 
						|
 | 
						|
	INIT_LIST_HEAD(&p->info_list);
 | 
						|
	return p;
 | 
						|
}
 | 
						|
 | 
						|
void vboxsf_dir_info_free(struct vboxsf_dir_info *p)
 | 
						|
{
 | 
						|
	struct list_head *list, *pos, *tmp;
 | 
						|
 | 
						|
	list = &p->info_list;
 | 
						|
	list_for_each_safe(pos, tmp, list) {
 | 
						|
		struct vboxsf_dir_buf *b;
 | 
						|
 | 
						|
		b = list_entry(pos, struct vboxsf_dir_buf, head);
 | 
						|
		vboxsf_dir_buf_free(b);
 | 
						|
	}
 | 
						|
	kfree(p);
 | 
						|
}
 | 
						|
 | 
						|
int vboxsf_dir_read_all(struct vboxsf_sbi *sbi, struct vboxsf_dir_info *sf_d,
 | 
						|
			u64 handle)
 | 
						|
{
 | 
						|
	struct vboxsf_dir_buf *b;
 | 
						|
	u32 entries, size;
 | 
						|
	int err = 0;
 | 
						|
	void *buf;
 | 
						|
 | 
						|
	/* vboxsf_dirinfo returns 1 on end of dir */
 | 
						|
	while (err == 0) {
 | 
						|
		b = vboxsf_dir_buf_alloc(&sf_d->info_list);
 | 
						|
		if (!b) {
 | 
						|
			err = -ENOMEM;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
 | 
						|
		buf = b->buf;
 | 
						|
		size = b->free;
 | 
						|
 | 
						|
		err = vboxsf_dirinfo(sbi->root, handle, NULL, 0, 0,
 | 
						|
				     &size, buf, &entries);
 | 
						|
		if (err < 0)
 | 
						|
			break;
 | 
						|
 | 
						|
		b->entries += entries;
 | 
						|
		b->free -= size;
 | 
						|
		b->used += size;
 | 
						|
	}
 | 
						|
 | 
						|
	if (b && b->used == 0)
 | 
						|
		vboxsf_dir_buf_free(b);
 | 
						|
 | 
						|
	/* -EILSEQ means the host could not translate a filename, ignore */
 | 
						|
	if (err > 0 || err == -EILSEQ)
 | 
						|
		err = 0;
 | 
						|
 | 
						|
	return err;
 | 
						|
}
 |