mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-26 02:39:48 +00:00
![Hans de Goede](/assets/img/avatar_default.png)
VirtualBox hosts can share folders with guests, this commit adds a VFS driver implementing the Linux-guest side of this, allowing folders exported by the host to be mounted under Linux. This driver depends on the guest <-> host IPC functions exported by the vboxguest driver. Acked-by: Christoph Hellwig <hch@infradead.org> Signed-off-by: Hans de Goede <hdegoede@redhat.com> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
551 lines
12 KiB
C
551 lines
12 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] */
|
|
void vboxsf_init_inode(struct vboxsf_sbi *sbi, struct inode *inode,
|
|
const struct shfl_fsobjinfo *info)
|
|
{
|
|
const struct shfl_fsobjattr *attr;
|
|
s64 allocated;
|
|
int 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)) {
|
|
inode->i_mode = sbi->o.dmode_set ? sbi->o.dmode : mode;
|
|
inode->i_mode &= ~sbi->o.dmask;
|
|
inode->i_mode |= S_IFDIR;
|
|
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 (SHFL_IS_SYMLINK(attr->mode)) {
|
|
inode->i_mode = sbi->o.fmode_set ? sbi->o.fmode : mode;
|
|
inode->i_mode &= ~sbi->o.fmask;
|
|
inode->i_mode |= S_IFLNK;
|
|
inode->i_op = &vboxsf_lnk_iops;
|
|
set_nlink(inode, 1);
|
|
} else {
|
|
inode->i_mode = sbi->o.fmode_set ? sbi->o.fmode : mode;
|
|
inode->i_mode &= ~sbi->o.fmask;
|
|
inode->i_mode |= S_IFREG;
|
|
inode->i_op = &vboxsf_reg_iops;
|
|
inode->i_fop = &vboxsf_reg_fops;
|
|
set_nlink(inode, 1);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
vboxsf_init_inode(sbi, inode, &info);
|
|
|
|
/*
|
|
* 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(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;
|
|
/* fall-through */
|
|
default:
|
|
err = vboxsf_inode_revalidate(dentry);
|
|
}
|
|
if (err)
|
|
return err;
|
|
|
|
generic_fillattr(d_inode(dentry), kstat);
|
|
return 0;
|
|
}
|
|
|
|
int vboxsf_setattr(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;
|
|
}
|