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. Signed-off-by: Hans de Goede <hdegoede@xxxxxxxxxx> --- fs/Kconfig | 1 + fs/Makefile | 1 + fs/vboxsf/Kconfig | 9 + fs/vboxsf/Makefile | 3 + fs/vboxsf/dir.c | 648 +++++++++++++++++++++++++++++ fs/vboxsf/file.c | 416 +++++++++++++++++++ fs/vboxsf/shfl_hostintf.h | 919 +++++++++++++++++++++++++++++++++++++++++ fs/vboxsf/super.c | 430 +++++++++++++++++++ fs/vboxsf/utils.c | 589 ++++++++++++++++++++++++++ fs/vboxsf/vboxsf_wrappers.c | 365 ++++++++++++++++ fs/vboxsf/vboxsf_wrappers.h | 46 +++ fs/vboxsf/vfsmod.h | 104 +++++ include/uapi/linux/vbsfmount.h | 62 +++ 13 files changed, 3593 insertions(+) create mode 100644 fs/vboxsf/Kconfig create mode 100644 fs/vboxsf/Makefile create mode 100644 fs/vboxsf/dir.c create mode 100644 fs/vboxsf/file.c create mode 100644 fs/vboxsf/shfl_hostintf.h create mode 100644 fs/vboxsf/super.c create mode 100644 fs/vboxsf/utils.c create mode 100644 fs/vboxsf/vboxsf_wrappers.c create mode 100644 fs/vboxsf/vboxsf_wrappers.h create mode 100644 fs/vboxsf/vfsmod.h create mode 100644 include/uapi/linux/vbsfmount.h diff --git a/fs/Kconfig b/fs/Kconfig index 7aee6d699fd6..7f80ad1cf591 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -248,6 +248,7 @@ source "fs/pstore/Kconfig" source "fs/sysv/Kconfig" source "fs/ufs/Kconfig" source "fs/exofs/Kconfig" +source "fs/vboxsf/Kconfig" endif # MISC_FILESYSTEMS diff --git a/fs/Makefile b/fs/Makefile index ef772f1eaff8..3057830f112a 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -129,3 +129,4 @@ obj-y += exofs/ # Multiple modules obj-$(CONFIG_CEPH_FS) += ceph/ obj-$(CONFIG_PSTORE) += pstore/ obj-$(CONFIG_EFIVAR_FS) += efivarfs/ +obj-$(CONFIG_VBOXSF_FS) += vboxsf/ diff --git a/fs/vboxsf/Kconfig b/fs/vboxsf/Kconfig new file mode 100644 index 000000000000..620e2232969c --- /dev/null +++ b/fs/vboxsf/Kconfig @@ -0,0 +1,9 @@ +config VBOXSF_FS + tristate "VirtualBox guest shared folder (vboxsf) support" + depends on VBOXGUEST + help + VirtualBox hosts can share folders with guests, this driver + implements the Linux-guest side of this allowing folders exported + by the host to be mounted under Linux. + + If you want to use shared folders in VirtualBox guests, answer Y or M. diff --git a/fs/vboxsf/Makefile b/fs/vboxsf/Makefile new file mode 100644 index 000000000000..fcbd488f9eec --- /dev/null +++ b/fs/vboxsf/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_VBOXSF_FS) += vboxsf.o + +vboxsf-objs := dir.o file.o utils.o vboxsf_wrappers.o super.o diff --git a/fs/vboxsf/dir.c b/fs/vboxsf/dir.c new file mode 100644 index 000000000000..7650f92041f7 --- /dev/null +++ b/fs/vboxsf/dir.c @@ -0,0 +1,648 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * VirtualBox Guest Shared Folders support: Directory inode and file operations + * + * Copyright (C) 2006-2016 Oracle Corporation + */ + +#include <linux/namei.h> +#include <linux/vbox_utils.h> +#include "vfsmod.h" + +/** + * Open a directory. Read the complete content into a buffer. + * Return: 0 or negative errno value. + * @inode inode + * @file file + */ +static int sf_dir_open(struct inode *inode, struct file *file) +{ + int err; + struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb); + struct sf_dir_info *sf_d; + struct sf_inode_info *sf_i = GET_INODE_INFO(inode); + struct shfl_createparms params = {}; + + if (file->private_data) + return 0; + + sf_d = sf_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(sf_g->root, sf_i->path, ¶ms); + if (err == 0) { + if (params.result == SHFL_FILE_EXISTS) { + err = sf_dir_read_all(sf_g, sf_i, sf_d, params.handle); + if (!err) + file->private_data = sf_d; + } else + err = -ENOENT; + + vboxsf_close(sf_g->root, params.handle); + } + + if (err) + sf_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) + sf_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) +{ + loff_t cur; + struct sf_glob_info *sf_g; + struct sf_dir_info *sf_d; + struct sf_inode_info *sf_i; + struct inode *inode; + struct list_head *pos, *list; + + inode = GET_F_DENTRY(dir)->d_inode; + sf_i = GET_INODE_INFO(inode); + sf_g = GET_GLOB_INFO(inode->i_sb); + sf_d = dir->private_data; + + if (sf_i->force_reread) { + int err; + struct shfl_createparms params = {}; + + 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(sf_g->root, sf_i->path, ¶ms); + if (err) + return err; + + if (params.result != SHFL_FILE_EXISTS) { + sf_dir_info_free(sf_d); + return -ENOENT; + } + + sf_dir_info_empty(sf_d); + err = sf_dir_read_all(sf_g, sf_i, sf_d, params.handle); + vboxsf_close(sf_g->root, params.handle); + if (err) + return err; + + sf_i->force_reread = 0; + } + + cur = 0; + list = &sf_d->info_list; + list_for_each(pos, list) { + struct sf_dir_buf *b; + struct shfl_dirinfo *info; + loff_t i; + + b = list_entry(pos, struct sf_dir_buf, head); + 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); + } + + *d_type = sf_get_d_type(info->info.attr.mode); + + return sf_nlscpy(sf_g, d_name, NAME_MAX, + info->name.string.utf8, info->name.length); + } + + return 1; +} + +/** + * This is called when vfs wants to populate internal buffers with + * directory [dir]s contents. [opaque] is an argument to the + * [filldir]. [filldir] magically modifies it's argument - [opaque] + * and takes following additional arguments (which i in turn get from + * the host via sf_getdent): + * + * name : name of the entry (i must also supply it's length huh?) + * type : type of the entry (FILE | DIR | etc) (i ellect to use DT_UNKNOWN) + * pos : position/index of the entry + * ino : inode number of the entry (i fake those) + * + * [dir] contains: + * f_pos : cursor into the directory listing + * private_data : mean of communication with the host side + * + * Extract elements from the directory listing (incrementing f_pos + * along the way) and feed them to [filldir] until: + * + * a. there are no more entries (i.e. sf_getdent set done to 1) + * b. failure to compute fake inode number + * c. filldir returns an error (see comment on that) + * Return: 0 or negative errno value. + * @dir Directory to read + * @ctx Directory context in which to store read elements + */ +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; + } + + /* 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; + ctx->pos += 1; + } +} + +const struct file_operations sf_dir_fops = { + .open = sf_dir_open, + .iterate = sf_dir_iterate, + .release = sf_dir_release, + .read = generic_read_dir, + .llseek = generic_file_llseek, +}; + +/* + * This is called during name resolution/lookup to check if the [dentry] in the + * cache is still valid. the job is handled by [sf_inode_revalidate]. + */ +static int sf_dentry_revalidate(struct dentry *dentry, unsigned int flags) +{ + if (flags & LOOKUP_RCU) + return -ECHILD; + + if (sf_inode_revalidate(dentry)) + return 0; + + return 1; +} + +static const struct dentry_operations sf_dentry_ops = { + .d_revalidate = sf_dentry_revalidate +}; + +/* iops */ + +/** + * This is called when vfs failed to locate dentry in the cache. The + * job of this function is to allocate inode and link it to dentry. + * [dentry] contains the name to be looked in the [parent] directory. + * Failure to locate the name is not a "hard" error, in this case NULL + * inode is added to [dentry] and vfs should proceed trying to create + * the entry via other means. NULL(or "positive" pointer) ought to be + * returned in case of success and "negative" pointer on error + * Return: NULL on success, ERR_PTR on failure. + * @parent inode of the dentry parent-directory + * @dentry dentry to populate + * @flags flags + */ +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 shfl_string *path; + struct inode *inode; + ino_t ino; + int err; + + sf_g = GET_GLOB_INFO(parent->i_sb); + sf_i = GET_INODE_INFO(parent); + + err = sf_path_from_dentry(__func__, sf_g, sf_i, dentry, &path); + if (err) + return ERR_PTR(err); + + err = sf_stat(__func__, sf_g, path, &fsinfo, 1); + if (err) { + kfree(path); + 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) { + kfree(path); + return ERR_PTR(-ENOMEM); + } + + GET_INODE_INFO(inode)->path = path; + sf_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); + return NULL; +} + +/** + * This should allocate memory for sf_inode_info, compute a unique inode + * number, get an inode from vfs, initialize inode info, instantiate + * dentry. + * Return: 0 or negative errno value. + * @parent inode entry of the directory + * @dentry directory cache entry + * @path path name + * @info file information + * @handle handle + */ +static int sf_instantiate(struct inode *parent, struct dentry *dentry, + struct shfl_string *path, 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) + return -ENOMEM; + + sf_i = GET_INODE_INFO(inode); + sf_i->path = path; + sf_i->force_restat = 1; + sf_i->handle = handle; + sf_init_inode(sf_g, inode, info); + + d_instantiate(dentry, inode); + unlock_new_inode(inode); + + return 0; +} + +/** + * Create a new regular file / directory. + * Return: 0 or negative errno value. + * @parent inode of the directory + * @dentry directory cache entry + * @mode file mode + * @is_dir true if directory, false otherwise + */ +static int sf_create_aux(struct inode *parent, struct dentry *dentry, + umode_t mode, int is_dir) +{ + int err; + struct shfl_createparms params = {}; + struct shfl_string *path; + struct sf_inode_info *sf_i = GET_INODE_INFO(parent); + struct sf_glob_info *sf_g = GET_GLOB_INFO(parent->i_sb); + + err = sf_path_from_dentry(__func__, sf_g, sf_i, dentry, &path); + if (err) + return err; + + params.handle = SHFL_HANDLE_NIL; + params.create_flags = 0 + | SHFL_CF_ACT_CREATE_IF_NEW + | SHFL_CF_ACT_FAIL_IF_EXISTS + | SHFL_CF_ACCESS_READWRITE | (is_dir ? SHFL_CF_DIRECTORY : 0); + params.info.attr.mode = 0 + | (is_dir ? SHFL_TYPE_DIRECTORY : SHFL_TYPE_FILE) + | (mode & 0777); + params.info.attr.additional = SHFLFSOBJATTRADD_NOTHING; + + err = vboxsf_create(sf_g->root, path, ¶ms); + if (err) + goto free_path; + + if (params.result != SHFL_FILE_CREATED) { + err = -EPERM; + goto free_path; + } + + err = sf_instantiate(parent, dentry, path, ¶ms.info, + is_dir ? SHFL_HANDLE_NIL : params.handle); + if (err) + goto close_handle; + + /* + * Don't close this handle right now. We assume that the same file is + * opened with sf_reg_open() and later closed with sf_reg_close(). Save + * the handle in between. Does not apply to directories. True? + */ + if (is_dir) + vboxsf_close(sf_g->root, params.handle); + + sf_i->force_restat = 1; + return 0; + +close_handle: + vboxsf_close(sf_g->root, params.handle); + +free_path: + kfree(path); + return err; +} + +/** + * Create a new regular file. + * Return: 0 or negative errno value. + * @parent inode of the directory + * @dentry directory cache entry + * @mode file mode + * @excl Possible O_EXCL... + */ +static int sf_create(struct inode *parent, struct dentry *dentry, umode_t mode, + bool excl) +{ + return sf_create_aux(parent, dentry, mode, 0); +} + +/** + * Create a new directory. + * Return: 0 or negative errno value. + * @parent inode of the directory + * @dentry directory cache entry + * @mode file mode + */ +static int sf_mkdir(struct inode *parent, struct dentry *dentry, umode_t mode) +{ + return sf_create_aux(parent, dentry, mode, 1); +} + +/** + * Remove a regular file / directory. + * Return: 0 or negative errno value. + * @parent inode of the directory + * @dentry directory cache entry + * @is_dir true if directory, false otherwise + */ +static int sf_unlink_aux(struct inode *parent, struct dentry *dentry, + int is_dir) +{ + int err; + struct sf_glob_info *sf_g = GET_GLOB_INFO(parent->i_sb); + struct sf_inode_info *sf_i = GET_INODE_INFO(parent); + struct shfl_string *path; + uint32_t flags; + + err = sf_path_from_dentry(__func__, sf_g, sf_i, dentry, &path); + if (err) + return err; + + flags = is_dir ? SHFL_REMOVE_DIR : SHFL_REMOVE_FILE; + if (dentry + && dentry->d_inode + && ((dentry->d_inode->i_mode & S_IFLNK) == S_IFLNK)) + flags |= SHFL_REMOVE_SYMLINK; + + err = vboxsf_remove(sf_g->root, path, flags); + if (err) + goto free_path; + + /* directory access/change time changed */ + sf_i->force_restat = 1; + /* directory content changed */ + sf_i->force_reread = 1; + +free_path: + kfree(path); + return err; +} + +/** + * 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); + u32 shfl_flags = SHFL_RENAME_FILE | SHFL_RENAME_REPLACE_IF_EXISTS; + struct sf_inode_info *sf_old_i = GET_INODE_INFO(old_parent); + struct sf_inode_info *sf_new_i = GET_INODE_INFO(new_parent); + /* + * As we save the relative path inside the inode structure, + * we need to change this if the rename is successful. + */ + struct sf_inode_info *sf_file_i = GET_INODE_INFO(old_dentry->d_inode); + struct shfl_string *path; + int err; + + if (flags) + return -EINVAL; + + if (sf_g != GET_GLOB_INFO(new_parent->i_sb)) + return -EINVAL; + + err = sf_path_from_dentry(__func__, sf_g, sf_new_i, new_dentry, &path); + if (err) + return err; + + if (old_dentry->d_inode->i_mode & S_IFDIR) + shfl_flags = 0; + + err = vboxsf_rename(sf_g->root, sf_file_i->path, path, shfl_flags); + if (err == 0) { + /* Set the new relative path in the inode. */ + kfree(sf_file_i->path); + sf_file_i->path = path; + sf_new_i->force_restat = 1; + sf_old_i->force_restat = 1; + } else { + kfree(path); + } + + return err; +} + +static int sf_symlink(struct inode *parent, struct dentry *dentry, + const char *symname) +{ + int err; + struct sf_inode_info *sf_i; + struct sf_glob_info *sf_g; + struct shfl_string *path, *ssymname; + struct shfl_fsobjinfo info; + int symname_len = strlen(symname) + 1; + + sf_g = GET_GLOB_INFO(parent->i_sb); + sf_i = GET_INODE_INFO(parent); + + err = sf_path_from_dentry(__func__, sf_g, sf_i, dentry, &path); + if (err) + return err; + + ssymname = kmalloc(SHFLSTRING_HEADER_SIZE + symname_len, GFP_KERNEL); + if (!ssymname) { + err = -ENOMEM; + goto free_path; + } + ssymname->length = symname_len - 1; + ssymname->size = symname_len; + memcpy(ssymname->string.utf8, symname, symname_len); + + err = vboxsf_symlink(sf_g->root, path, ssymname, &info); + kfree(ssymname); + + if (err) + goto free_path; + + err = sf_instantiate(parent, dentry, path, &info, SHFL_HANDLE_NIL); + if (err) + goto free_path; + + sf_i->force_restat = 1; + return 0; + +free_path: + kfree(path); + return err; +} + +const struct inode_operations sf_dir_iops = { + .lookup = sf_lookup, + .create = sf_create, + .mkdir = sf_mkdir, + .rmdir = sf_rmdir, + .unlink = sf_unlink, + .rename = sf_rename, + .getattr = sf_getattr, + .setattr = sf_setattr, + .symlink = sf_symlink +}; diff --git a/fs/vboxsf/file.c b/fs/vboxsf/file.c new file mode 100644 index 000000000000..8e085a28cf91 --- /dev/null +++ b/fs/vboxsf/file.c @@ -0,0 +1,416 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * VirtualBox Guest Shared Folders support: Regular file inode and file ops. + * + * Copyright (C) 2006-2016 Oracle Corporation + */ + +#include <linux/sizes.h> +#include "vfsmod.h" + +/** + * Read from a regular file. + * Return: The number of bytes read on success, negative errno value otherwise + * @file the file + * @buf the buffer + * @size length of the buffer + * @off offset within the file + */ +static ssize_t sf_reg_read(struct file *file, char *buf, size_t size, + loff_t *off) +{ + struct inode *inode = GET_F_DENTRY(file)->d_inode; + struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb); + struct sf_reg_info *sf_r = file->private_data; + u64 pos = *off; + u32 nread; + int err; + + if (!size) + return 0; + + if (size > SHFL_MAX_RW_COUNT) + nread = SHFL_MAX_RW_COUNT; + else + nread = size; + + err = vboxsf_read(sf_g->root, sf_r->handle, pos, &nread, buf, true); + if (err) + return err; + + *off += nread; + return nread; +} + +/** + * Write to a regular file. + * Return: The number of bytes written on success, negative errno val otherwise + * @file the file + * @buf the buffer + * @size length of the buffer + * @off offset within the file + */ +static ssize_t sf_reg_write(struct file *file, const char *buf, size_t size, + loff_t *off) +{ + struct inode *inode = GET_F_DENTRY(file)->d_inode; + 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; + } + + if (!size) + return 0; + + if (size > SHFL_MAX_RW_COUNT) + nwritten = SHFL_MAX_RW_COUNT; + else + nwritten = size; + + err = vboxsf_write(sf_g->root, sf_r->handle, pos, &nwritten, buf, true); + if (err) + return err; + + *off += nwritten; + if (*off > inode->i_size) + i_size_write(inode, *off); + + sf_i->force_restat = 1; + return nwritten; +} + +/** + * Open a regular file. + * Return: 0 or negative errno value. + * @inode inode + * @file file + */ +static int sf_reg_open(struct inode *inode, struct file *file) +{ + struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb); + struct sf_inode_info *sf_i = GET_INODE_INFO(inode); + struct sf_reg_info *sf_r; + struct shfl_createparms params = {}; + int err; + + sf_r = kmalloc(sizeof(*sf_r), GFP_KERNEL); + if (!sf_r) + return -ENOMEM; + + /* Already open? */ + if (sf_i->handle != SHFL_HANDLE_NIL) { + /* + * This inode was created with sf_create_aux(). Check the + * create_flags: O_CREAT, O_TRUNC: inherent true (file was + * just created). + * Not sure about the access flags (SHFL_CF_ACCESS_*). + */ + sf_i->force_restat = 1; + sf_r->handle = sf_i->handle; + sf_i->handle = SHFL_HANDLE_NIL; + sf_i->file = file; + file->private_data = sf_r; + return 0; + } + + params.handle = SHFL_HANDLE_NIL; + + /* + * We check the value of params.handle afterwards to find out if + * the call succeeded or failed, as the API does not seem to cleanly + * distinguish error and informational messages. + * + * Furthermore, we must set params.handle to SHFL_HANDLE_NIL to + * make the shared folders host service use our mode parameter. + */ + if (file->f_flags & O_CREAT) { + params.create_flags |= SHFL_CF_ACT_CREATE_IF_NEW; + /* + * We ignore O_EXCL, as the Linux kernel seems to call create + * beforehand itself, so O_EXCL should always fail. + */ + if (file->f_flags & O_TRUNC) + params.create_flags |= SHFL_CF_ACT_OVERWRITE_IF_EXISTS; + else + params.create_flags |= SHFL_CF_ACT_OPEN_IF_EXISTS; + } else { + params.create_flags |= SHFL_CF_ACT_FAIL_IF_NEW; + if (file->f_flags & O_TRUNC) + params.create_flags |= SHFL_CF_ACT_OVERWRITE_IF_EXISTS; + } + + switch (file->f_flags & O_ACCMODE) { + case O_RDONLY: + params.create_flags |= SHFL_CF_ACCESS_READ; + break; + + case O_WRONLY: + params.create_flags |= SHFL_CF_ACCESS_WRITE; + break; + + case O_RDWR: + params.create_flags |= SHFL_CF_ACCESS_READWRITE; + break; + + default: + WARN_ON(1); + } + + if (file->f_flags & O_APPEND) + params.create_flags |= SHFL_CF_ACCESS_APPEND; + + params.info.attr.mode = inode->i_mode; + err = vboxsf_create(sf_g->root, sf_i->path, ¶ms); + if (err == 0 && params.handle == SHFL_HANDLE_NIL) + err = (params.result == SHFL_FILE_EXISTS) ? -EEXIST : -ENOENT; + if (err) { + kfree(sf_r); + return err; + } + + sf_i->force_restat = 1; + sf_r->handle = params.handle; + sf_i->file = file; + file->private_data = sf_r; + return 0; +} + +/** + * Close a regular file. + * Return: 0 or negative errno value. + * @inode inode + * @file file + */ +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; +} + +static int sf_reg_fault(struct vm_fault *vmf) +{ + struct vm_area_struct *vma = vmf->vma; + struct file *file = vma->vm_file; + struct inode *inode = GET_F_DENTRY(file)->d_inode; + struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb); + struct sf_reg_info *sf_r = file->private_data; + u32 nread = PAGE_SIZE; + struct page *page; + u64 off; + u8 *buf; + int err; + + if (vmf->pgoff > vma->vm_end) + return VM_FAULT_SIGBUS; + + /* No GFP_HIGHUSER, the page must have a valid virtual address. */ + page = alloc_page(GFP_USER); + if (!page) + return VM_FAULT_OOM; + + buf = kmap(page); + off = (vmf->pgoff << PAGE_SHIFT); + err = vboxsf_read(sf_g->root, sf_r->handle, off, &nread, buf, false); + if (err) { + kunmap(page); + put_page(page); + return VM_FAULT_SIGBUS; + } + + if (!nread) + clear_user_page(page_address(page), vmf->pgoff, page); + else + memset(buf + nread, 0, PAGE_SIZE - nread); + + flush_dcache_page(page); + kunmap(page); + vmf->page = page; + return 0; +} + +static const struct vm_operations_struct sf_vma_ops = { + .fault = sf_reg_fault +}; + +static int sf_reg_mmap(struct file *file, struct vm_area_struct *vma) +{ + if (vma->vm_flags & VM_SHARED) + return -EINVAL; + + vma->vm_ops = &sf_vma_ops; + return 0; +} + +const struct file_operations sf_reg_fops = { + .read = sf_reg_read, + .open = sf_reg_open, + .write = sf_reg_write, + .release = sf_reg_release, + .mmap = sf_reg_mmap, + .splice_read = generic_file_splice_read, + .read_iter = generic_file_read_iter, + .write_iter = generic_file_write_iter, + .fsync = noop_fsync, + .llseek = generic_file_llseek, +}; + +const struct inode_operations sf_reg_iops = { + .getattr = sf_getattr, + .setattr = sf_setattr +}; + +static int sf_readpage(struct file *file, struct page *page) +{ + struct inode *inode = GET_F_DENTRY(file)->d_inode; + struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb); + struct sf_reg_info *sf_r = file->private_data; + u64 off = page_offset(page); + u32 nread = PAGE_SIZE; + u8 *buf; + int err; + + buf = kmap(page); + + err = vboxsf_read(sf_g->root, sf_r->handle, off, &nread, buf, false); + if (err == 0) { + memset(&buf[nread], 0, PAGE_SIZE - nread); + flush_dcache_page(page); + SetPageUptodate(page); + } else { + SetPageError(page); + } + + kunmap(page); + unlock_page(page); + return err; +} + +static int sf_writepage(struct page *page, struct writeback_control *wbc) +{ + struct address_space *mapping = page->mapping; + struct inode *inode = mapping->host; + struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb); + struct sf_inode_info *sf_i = GET_INODE_INFO(inode); + struct file *file = sf_i->file; + struct sf_reg_info *sf_r = file->private_data; + int end_index = inode->i_size >> PAGE_SHIFT; + u64 off = page_offset(page); + u32 nwritten = PAGE_SIZE; + u8 *buf; + int err; + + if (page->index >= end_index) + nwritten = inode->i_size & (PAGE_SIZE - 1); + + buf = kmap(page); + err = vboxsf_write(sf_g->root, sf_r->handle, off, &nwritten, buf, false); + kunmap(page); + + if (err == 0) + ClearPageError(page); + else + ClearPageUptodate(page); + + unlock_page(page); + return err; +} + +int sf_write_begin(struct file *file, struct address_space *mapping, loff_t pos, + unsigned int len, unsigned int flags, struct page **pagep, + void **fsdata) +{ + return simple_write_begin(file, mapping, pos, len, flags, pagep, + fsdata); +} + +int sf_write_end(struct file *file, struct address_space *mapping, loff_t pos, + unsigned int len, unsigned int copied, struct page *page, + void *fsdata) +{ + struct inode *inode = mapping->host; + struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb); + struct sf_reg_info *sf_r = file->private_data; + unsigned int from = pos & (PAGE_SIZE - 1); + u32 nwritten = len; + u8 *buf; + int err; + + buf = kmap(page); + err = vboxsf_write(sf_g->root, sf_r->handle, pos, &nwritten, + buf + from, false); + kunmap(page); + + if (err) + goto out; + + if (!PageUptodate(page) && nwritten == PAGE_SIZE) + SetPageUptodate(page); + + pos += nwritten; + if (pos > inode->i_size) + i_size_write(inode, pos); + +out: + unlock_page(page); + put_page(page); + + return err; +} + +const struct address_space_operations sf_reg_aops = { + .readpage = sf_readpage, + .writepage = sf_writepage, + .write_begin = sf_write_begin, + .write_end = sf_write_end, +}; + +static const char *sf_get_link(struct dentry *dentry, struct inode *inode, + struct delayed_call *done) +{ + struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb); + struct sf_inode_info *sf_i = GET_INODE_INFO(inode); + char *path; + int err; + + if (!dentry) + return ERR_PTR(-ECHILD); + + path = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!path) + return ERR_PTR(-ENOMEM); + + err = vboxsf_readlink(sf_g->root, sf_i->path, PATH_MAX, path); + if (err) { + kfree(path); + return ERR_PTR(err); + } + + set_delayed_call(done, kfree_link, path); + return path; +} + +const struct inode_operations sf_lnk_iops = { + .get_link = sf_get_link +}; diff --git a/fs/vboxsf/shfl_hostintf.h b/fs/vboxsf/shfl_hostintf.h new file mode 100644 index 000000000000..8666a5347a0b --- /dev/null +++ b/fs/vboxsf/shfl_hostintf.h @@ -0,0 +1,919 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR CDDL-1.0) */ +/* + * VirtualBox Shared Folders: host interface definition. + * + * Copyright (C) 2006-2016 Oracle Corporation + */ + +#ifndef SHFL_HOSTINTF_H +#define SHFL_HOSTINTF_H + +#include <linux/vbox_vmmdev_types.h> + +/* The max in/out buffer size for a FN_READ or FN_WRITE call */ +#define SHFL_MAX_RW_COUNT (16 * SZ_1M) + +/* + * Structures shared between guest and the service + * can be relocated and use offsets to point to variable + * length parts. + * + * Shared folders protocol works with handles. + * Before doing any action on a file system object, + * one have to obtain the object handle via a SHFL_FN_CREATE + * request. A handle must be closed with SHFL_FN_CLOSE. + */ + +/* Query mappings changes. */ +#define SHFL_FN_QUERY_MAPPINGS (1) +/* Query mappings changes. */ +#define SHFL_FN_QUERY_MAP_NAME (2) +/* Open/create object. */ +#define SHFL_FN_CREATE (3) +/* Close object handle. */ +#define SHFL_FN_CLOSE (4) +/* Read object content. */ +#define SHFL_FN_READ (5) +/* Write new object content. */ +#define SHFL_FN_WRITE (6) +/* Lock/unlock a range in the object. */ +#define SHFL_FN_LOCK (7) +/* List object content. */ +#define SHFL_FN_LIST (8) +/* Query/set object information. */ +#define SHFL_FN_INFORMATION (9) +/* Remove object */ +#define SHFL_FN_REMOVE (11) +/* Map folder (legacy) */ +#define SHFL_FN_MAP_FOLDER_OLD (12) +/* Unmap folder */ +#define SHFL_FN_UNMAP_FOLDER (13) +/* Rename object (possibly moving it to another directory) */ +#define SHFL_FN_RENAME (14) +/* Flush file */ +#define SHFL_FN_FLUSH (15) +#define SHFL_FN_SET_UTF8 (16) +#define SHFL_CPARMS_SET_UTF8 0 +/* Map folder */ +#define SHFL_FN_MAP_FOLDER (17) +/* Read symlink destination (as of VBox 4.0) */ +#define SHFL_FN_READLINK (18) +/* Create symlink (as of VBox 4.0) */ +#define SHFL_FN_SYMLINK (19) +/* Ask host to show symlinks (as of VBox 4.0) */ +#define SHFL_FN_SET_SYMLINKS (20) +#define SHFL_CPARMS_SET_SYMLINKS 0 + +/* Root handles for a mapping are of type u32, Root handles are unique. */ +#define SHFL_ROOT_NIL ((u32)~0) + +/* Shared folders handle for an opened object are of type u64. */ +#define SHFL_HANDLE_NIL ((u64)~0LL) +#define SHFL_HANDLE_ROOT ((u64)0LL) + +/* Hardcoded maximum length (in chars) of a shared folder name. */ +#define SHFL_MAX_LEN (256) +/* Hardcoded maximum number of shared folder mapping available to the guest. */ +#define SHFL_MAX_MAPPINGS (64) + +/** Shared folder string buffer structure. */ +struct shfl_string { + /** Allocated size of the string member in bytes. */ + u16 size; + + /** Length of string without trailing nul in bytes. */ + u16 length; + + /** UTF-8 or UTF-16 string. Nul terminated. */ + union { + u8 utf8[1]; + u16 utf16[1]; + u16 ucs2[1]; /* misnomer, use utf16. */ + } string; +}; +VMMDEV_ASSERT_SIZE(shfl_string, 6); + +/* The size of shfl_string w/o the string part. */ +#define SHFLSTRING_HEADER_SIZE 4 + +/* Calculate size of the string. */ +static inline u32 shfl_string_buf_size(const struct shfl_string *string) +{ + return string ? SHFLSTRING_HEADER_SIZE + string->size : 0; +} + +/* Set user id on execution (S_ISUID). */ +#define SHFL_UNIX_ISUID 0004000U +/* Set group id on execution (S_ISGID). */ +#define SHFL_UNIX_ISGID 0002000U +/* Sticky bit (S_ISVTX / S_ISTXT). */ +#define SHFL_UNIX_ISTXT 0001000U + +/* Owner readable (S_IRUSR). */ +#define SHFL_UNIX_IRUSR 0000400U +/* Owner writable (S_IWUSR). */ +#define SHFL_UNIX_IWUSR 0000200U +/* Owner executable (S_IXUSR). */ +#define SHFL_UNIX_IXUSR 0000100U + +/* Group readable (S_IRGRP). */ +#define SHFL_UNIX_IRGRP 0000040U +/* Group writable (S_IWGRP). */ +#define SHFL_UNIX_IWGRP 0000020U +/* Group executable (S_IXGRP). */ +#define SHFL_UNIX_IXGRP 0000010U + +/* Other readable (S_IROTH). */ +#define SHFL_UNIX_IROTH 0000004U +/* Other writable (S_IWOTH). */ +#define SHFL_UNIX_IWOTH 0000002U +/* Other executable (S_IXOTH). */ +#define SHFL_UNIX_IXOTH 0000001U + +/* Named pipe (fifo) (S_IFIFO). */ +#define SHFL_TYPE_FIFO 0010000U +/* Character device (S_IFCHR). */ +#define SHFL_TYPE_DEV_CHAR 0020000U +/* Directory (S_IFDIR). */ +#define SHFL_TYPE_DIRECTORY 0040000U +/* Block device (S_IFBLK). */ +#define SHFL_TYPE_DEV_BLOCK 0060000U +/* Regular file (S_IFREG). */ +#define SHFL_TYPE_FILE 0100000U +/* Symbolic link (S_IFLNK). */ +#define SHFL_TYPE_SYMLINK 0120000U +/* Socket (S_IFSOCK). */ +#define SHFL_TYPE_SOCKET 0140000U +/* Whiteout (S_IFWHT). */ +#define SHFL_TYPE_WHITEOUT 0160000U +/* Type mask (S_IFMT). */ +#define SHFL_TYPE_MASK 0170000U + +/* Checks the mode flags indicate a directory (S_ISDIR). */ +#define SHFL_IS_DIRECTORY(m) (((m) & SHFL_TYPE_MASK) == SHFL_TYPE_DIRECTORY) +/* Checks the mode flags indicate a symbolic link (S_ISLNK). */ +#define SHFL_IS_SYMLINK(m) (((m) & SHFL_TYPE_MASK) == SHFL_TYPE_SYMLINK) + +/** The available additional information in a shfl_fsobjattr object. */ +enum shfl_fsobjattr_add { + /** No additional information is available / requested. */ + SHFLFSOBJATTRADD_NOTHING = 1, + /** + * The additional unix attributes (shfl_fsobjattr::u::unix_attr) are + * available / requested. + */ + SHFLFSOBJATTRADD_UNIX, + /** + * The additional extended attribute size (shfl_fsobjattr::u::size) is + * available / requested. + */ + SHFLFSOBJATTRADD_EASIZE, + /** + * The last valid item (inclusive). + * The valid range is SHFLFSOBJATTRADD_NOTHING thru + * SHFLFSOBJATTRADD_LAST. + */ + SHFLFSOBJATTRADD_LAST = SHFLFSOBJATTRADD_EASIZE, + + /** The usual 32-bit hack. */ + SHFLFSOBJATTRADD_32BIT_SIZE_HACK = 0x7fffffff +}; + +/** + * Additional unix Attributes, these are available when + * shfl_fsobjattr.additional == SHFLFSOBJATTRADD_UNIX. + */ +struct shfl_fsobjattr_unix { + /** + * The user owning the filesystem object (st_uid). + * This field is ~0U if not supported. + */ + u32 uid; + + /** + * The group the filesystem object is assigned (st_gid). + * This field is ~0U if not supported. + */ + u32 gid; + + /** + * 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; + + /** + * The device number of the device which this filesystem object resides + * on (st_dev). This field is 0 if this information is not available. + */ + u32 inode_id_device; + + /** + * The unique identifier (within the filesystem) of this filesystem + * object (st_ino). Together with inode_id_device, this field can be + * used as a OS wide unique id, when both their values are not 0. + * This field is 0 if the information is not available. + */ + u64 inode_id; + + /** + * User flags (st_flags). + * This field is 0 if this information is not available. + */ + u32 flags; + + /** + * The current generation number (st_gen). + * This field is 0 if this information is not available. + */ + u32 generation_id; + + /** + * The device number of a char. or block device type object (st_rdev). + * This field is 0 if the file isn't a char. or block device or when + * the OS doesn't use the major+minor device idenfication scheme. + */ + u32 device; +} __packed; + +/** Extended attribute size. */ +struct shfl_fsobjattr_easize { + /** Size of EAs. */ + s64 cb; +} __packed; + +/** Shared folder filesystem object attributes. */ +struct shfl_fsobjattr { + /** Mode flags (st_mode). SHFL_UNIX_*, SHFL_TYPE_*, and SHFL_DOS_*. */ + u32 mode; + + /** The additional attributes available. */ + enum shfl_fsobjattr_add additional; + + /** + * Additional attributes. + * + * Unless explicitly specified to an API, the API can provide additional + * data as it is provided by the underlying OS. + */ + union { + struct shfl_fsobjattr_unix unix_attr; + struct shfl_fsobjattr_easize size; + } __packed u; +} __packed; +VMMDEV_ASSERT_SIZE(shfl_fsobjattr, 44); + +struct shfl_timespec { + s64 ns_relative_to_unix_epoch; +}; + +/** Filesystem object information structure. */ +struct shfl_fsobjinfo { + /** + * Logical size (st_size). + * For normal files this is the size of the file. + * For symbolic links, this is the length of the path name contained + * in the symbolic link. + * For other objects this fields needs to be specified. + */ + s64 size; + + /** Disk allocation size (st_blocks * DEV_BSIZE). */ + s64 allocated; + + /** Time of last access (st_atime). */ + struct shfl_timespec access_time; + + /** Time of last data modification (st_mtime). */ + struct shfl_timespec modification_time; + + /** + * Time of last status change (st_ctime). + * If not available this is set to modification_time. + */ + struct shfl_timespec change_time; + + /** + * Time of file birth (st_birthtime). + * If not available this is set to change_time. + */ + struct shfl_timespec birth_time; + + /** Attributes. */ + struct shfl_fsobjattr attr; + +} __packed; +VMMDEV_ASSERT_SIZE(shfl_fsobjinfo, 92); + +/** + * result of an open/create request. + * Along with handle value the result code + * identifies what has happened while + * trying to open the object. + */ +enum shfl_create_result { + SHFL_NO_RESULT, + /** Specified path does not exist. */ + SHFL_PATH_NOT_FOUND, + /** Path to file exists, but the last component does not. */ + SHFL_FILE_NOT_FOUND, + /** File already exists and either has been opened or not. */ + SHFL_FILE_EXISTS, + /** New file was created. */ + SHFL_FILE_CREATED, + /** Existing file was replaced or overwritten. */ + SHFL_FILE_REPLACED +}; + +/* No flags. Initialization value. */ +#define SHFL_CF_NONE (0x00000000) + +/* + * Only lookup the object, do not return a handle. When this is set all other + * flags are ignored. + */ +#define SHFL_CF_LOOKUP (0x00000001) + +/* + * Open parent directory of specified object. + * Useful for the corresponding Windows FSD flag + * and for opening paths like \\dir\\*.* to search the 'dir'. + */ +#define SHFL_CF_OPEN_TARGET_DIRECTORY (0x00000002) + +/* Create/open a directory. */ +#define SHFL_CF_DIRECTORY (0x00000004) + +/* + * Open/create action to do if object exists + * and if the object does not exists. + * REPLACE file means atomically DELETE and CREATE. + * OVERWRITE file means truncating the file to 0 and + * setting new size. + * When opening an existing directory REPLACE and OVERWRITE + * actions are considered invalid, and cause returning + * FILE_EXISTS with NIL handle. + */ +#define SHFL_CF_ACT_MASK_IF_EXISTS (0x000000f0) +#define SHFL_CF_ACT_MASK_IF_NEW (0x00000f00) + +/* What to do if object exists. */ +#define SHFL_CF_ACT_OPEN_IF_EXISTS (0x00000000) +#define SHFL_CF_ACT_FAIL_IF_EXISTS (0x00000010) +#define SHFL_CF_ACT_REPLACE_IF_EXISTS (0x00000020) +#define SHFL_CF_ACT_OVERWRITE_IF_EXISTS (0x00000030) + +/* What to do if object does not exist. */ +#define SHFL_CF_ACT_CREATE_IF_NEW (0x00000000) +#define SHFL_CF_ACT_FAIL_IF_NEW (0x00000100) + +/* Read/write requested access for the object. */ +#define SHFL_CF_ACCESS_MASK_RW (0x00003000) + +/* No access requested. */ +#define SHFL_CF_ACCESS_NONE (0x00000000) +/* Read access requested. */ +#define SHFL_CF_ACCESS_READ (0x00001000) +/* Write access requested. */ +#define SHFL_CF_ACCESS_WRITE (0x00002000) +/* Read/Write access requested. */ +#define SHFL_CF_ACCESS_READWRITE (0x00003000) + +/* Requested share access for the object. */ +#define SHFL_CF_ACCESS_MASK_DENY (0x0000c000) + +/* Allow any access. */ +#define SHFL_CF_ACCESS_DENYNONE (0x00000000) +/* Do not allow read. */ +#define SHFL_CF_ACCESS_DENYREAD (0x00004000) +/* Do not allow write. */ +#define SHFL_CF_ACCESS_DENYWRITE (0x00008000) +/* Do not allow access. */ +#define SHFL_CF_ACCESS_DENYALL (0x0000c000) + +/* Requested access to attributes of the object. */ +#define SHFL_CF_ACCESS_MASK_ATTR (0x00030000) + +/* No access requested. */ +#define SHFL_CF_ACCESS_ATTR_NONE (0x00000000) +/* Read access requested. */ +#define SHFL_CF_ACCESS_ATTR_READ (0x00010000) +/* Write access requested. */ +#define SHFL_CF_ACCESS_ATTR_WRITE (0x00020000) +/* Read/Write access requested. */ +#define SHFL_CF_ACCESS_ATTR_READWRITE (0x00030000) + +/* + * The file is opened in append mode. + * Ignored if SHFL_CF_ACCESS_WRITE is not set. + */ +#define SHFL_CF_ACCESS_APPEND (0x00040000) + +/** Create parameters buffer struct for SHFL_FN_CREATE call */ +struct shfl_createparms { + /** Returned handle of opened object. */ + u64 handle; + + /** Returned result of the operation */ + enum shfl_create_result result; + + /** SHFL_CF_* */ + u32 create_flags; + + /** + * Attributes of object to create and + * returned actual attributes of opened/created object. + */ + struct shfl_fsobjinfo info; +} __packed; + +/** Shared Folder directory information */ +struct shfl_dirinfo { + /** Full information about the object. */ + struct shfl_fsobjinfo info; + /** + * The length of the short field (number of UTF16 chars). + * It is 16-bit for reasons of alignment. + */ + u16 short_name_len; + /** + * The short name for 8.3 compatibility. + * Empty string if not available. + */ + u16 short_name[14]; + struct shfl_string name; +}; + +/** Shared folder filesystem properties. */ +struct shfl_fsproperties { + /** + * The maximum size of a filesystem object name. + * This does not include the '\\0'. + */ + u32 max_component_len; + + /** + * True if the filesystem is remote. + * False if the filesystem is local. + */ + bool remote; + + /** + * True if the filesystem is case sensitive. + * False if the filesystem is case insensitive. + */ + bool case_sensitive; + + /** + * True if the filesystem is mounted read only. + * False if the filesystem is mounted read write. + */ + bool read_only; + + /** + * True if the filesystem can encode unicode object names. + * False if it can't. + */ + bool supports_unicode; + + /** + * True if the filesystem is compresses. + * False if it isn't or we don't know. + */ + bool compressed; + + /** + * True if the filesystem compresses of individual files. + * False if it doesn't or we don't know. + */ + bool file_compression; +}; +VMMDEV_ASSERT_SIZE(shfl_fsproperties, 12); + +struct shfl_volinfo { + s64 total_allocation_bytes; + s64 available_allocation_bytes; + u32 bytes_per_allocation_unit; + u32 bytes_per_sector; + u32 serial; + struct shfl_fsproperties properties; +}; + + +/** SHFL_FN_MAP_FOLDER Parameters structure. */ +struct shfl_map_folder { + /** + * pointer, in: + * Points to struct shfl_string buffer. + */ + struct vmmdev_hgcm_function_parameter path; + + /** + * pointer, out: SHFLROOT (u32) + * Root handle of the mapping which name is queried. + */ + struct vmmdev_hgcm_function_parameter root; + + /** + * pointer, in: UTF16 + * Path delimiter + */ + struct vmmdev_hgcm_function_parameter delimiter; + + /** + * pointer, in: SHFLROOT (u32) + * Case senstive flag + */ + struct vmmdev_hgcm_function_parameter case_sensitive; + +}; + +/* Number of parameters */ +#define SHFL_CPARMS_MAP_FOLDER (4) + + +/** SHFL_FN_UNMAP_FOLDER Parameters structure. */ +struct shfl_unmap_folder { + /** + * pointer, in: SHFLROOT (u32) + * Root handle of the mapping which name is queried. + */ + struct vmmdev_hgcm_function_parameter root; + +}; + +/* Number of parameters */ +#define SHFL_CPARMS_UNMAP_FOLDER (1) + + +/** SHFL_FN_CREATE Parameters structure. */ +struct shfl_create { + /** + * pointer, in: SHFLROOT (u32) + * Root handle of the mapping which name is queried. + */ + struct vmmdev_hgcm_function_parameter root; + + /** + * pointer, in: + * Points to struct shfl_string buffer. + */ + struct vmmdev_hgcm_function_parameter path; + + /** + * pointer, in/out: + * Points to struct shfl_createparms buffer. + */ + struct vmmdev_hgcm_function_parameter parms; + +}; + +/* Number of parameters */ +#define SHFL_CPARMS_CREATE (3) + + +/** SHFL_FN_CLOSE Parameters structure. */ +struct shfl_close { + /** + * pointer, in: SHFLROOT (u32) + * Root handle of the mapping which name is queried. + */ + struct vmmdev_hgcm_function_parameter root; + + /** + * value64, in: + * SHFLHANDLE (u64) of object to close. + */ + struct vmmdev_hgcm_function_parameter handle; + +}; + +/* Number of parameters */ +#define SHFL_CPARMS_CLOSE (2) + + +/** SHFL_FN_READ Parameters structure. */ +struct shfl_read { + /** + * pointer, in: SHFLROOT (u32) + * Root handle of the mapping which name is queried. + */ + struct vmmdev_hgcm_function_parameter root; + + /** + * value64, in: + * SHFLHANDLE (u64) of object to read from. + */ + struct vmmdev_hgcm_function_parameter handle; + + /** + * value64, in: + * Offset to read from. + */ + struct vmmdev_hgcm_function_parameter offset; + + /** + * value64, in/out: + * Bytes to read/How many were read. + */ + struct vmmdev_hgcm_function_parameter cb; + + /** + * pointer, out: + * Buffer to place data to. + */ + struct vmmdev_hgcm_function_parameter buffer; + +}; + +/* Number of parameters */ +#define SHFL_CPARMS_READ (5) + + +/** SHFL_FN_WRITE Parameters structure. */ +struct shfl_write { + /** + * pointer, in: SHFLROOT (u32) + * Root handle of the mapping which name is queried. + */ + struct vmmdev_hgcm_function_parameter root; + + /** + * value64, in: + * SHFLHANDLE (u64) of object to write to. + */ + struct vmmdev_hgcm_function_parameter handle; + + /** + * value64, in: + * Offset to write to. + */ + struct vmmdev_hgcm_function_parameter offset; + + /** + * value64, in/out: + * Bytes to write/How many were written. + */ + struct vmmdev_hgcm_function_parameter cb; + + /** + * pointer, in: + * Data to write. + */ + struct vmmdev_hgcm_function_parameter buffer; + +}; + +/* Number of parameters */ +#define SHFL_CPARMS_WRITE (5) + + +/* + * SHFL_FN_LIST + * Listing information includes variable length RTDIRENTRY[EX] structures. + */ + +#define SHFL_LIST_NONE 0 +#define SHFL_LIST_RETURN_ONE 1 + +/** SHFL_FN_LIST Parameters structure. */ +struct shfl_list { + /** + * pointer, in: SHFLROOT (u32) + * Root handle of the mapping which name is queried. + */ + struct vmmdev_hgcm_function_parameter root; + + /** + * value64, in: + * SHFLHANDLE (u64) of object to be listed. + */ + struct vmmdev_hgcm_function_parameter handle; + + /** + * value32, in: + * List flags SHFL_LIST_*. + */ + struct vmmdev_hgcm_function_parameter flags; + + /** + * value32, in/out: + * Bytes to be used for listing information/How many bytes were used. + */ + struct vmmdev_hgcm_function_parameter cb; + + /** + * pointer, in/optional + * Points to struct shfl_string buffer that specifies a search path. + */ + struct vmmdev_hgcm_function_parameter path; + + /** + * pointer, out: + * Buffer to place listing information to. (struct shfl_dirinfo) + */ + struct vmmdev_hgcm_function_parameter buffer; + + /** + * value32, in/out: + * Indicates a key where the listing must be resumed. + * in: 0 means start from begin of object. + * out: 0 means listing completed. + */ + struct vmmdev_hgcm_function_parameter resume_point; + + /** + * pointer, out: + * Number of files returned + */ + struct vmmdev_hgcm_function_parameter file_count; +}; + +/* Number of parameters */ +#define SHFL_CPARMS_LIST (8) + + +/** SHFL_FN_READLINK Parameters structure. */ +struct shfl_readLink { + /** + * pointer, in: SHFLROOT (u32) + * Root handle of the mapping which name is queried. + */ + struct vmmdev_hgcm_function_parameter root; + + /** + * pointer, in: + * Points to struct shfl_string buffer. + */ + struct vmmdev_hgcm_function_parameter path; + + /** + * pointer, out: + * Buffer to place data to. + */ + struct vmmdev_hgcm_function_parameter buffer; + +}; + +/* Number of parameters */ +#define SHFL_CPARMS_READLINK (3) + + +/* SHFL_FN_INFORMATION */ + +/* Mask of Set/Get bit. */ +#define SHFL_INFO_MODE_MASK (0x1) +/* Get information */ +#define SHFL_INFO_GET (0x0) +/* Set information */ +#define SHFL_INFO_SET (0x1) + +/* Get name of the object. */ +#define SHFL_INFO_NAME (0x2) +/* Set size of object (extend/trucate); only applies to file objects */ +#define SHFL_INFO_SIZE (0x4) +/* Get/Set file object info. */ +#define SHFL_INFO_FILE (0x8) +/* Get volume information. */ +#define SHFL_INFO_VOLUME (0x10) + +/** SHFL_FN_INFORMATION Parameters structure. */ +struct shfl_information { + /** + * pointer, in: SHFLROOT (u32) + * Root handle of the mapping which name is queried. + */ + struct vmmdev_hgcm_function_parameter root; + + /** + * value64, in: + * SHFLHANDLE (u64) of object to be listed. + */ + struct vmmdev_hgcm_function_parameter handle; + + /** + * value32, in: + * SHFL_INFO_* + */ + struct vmmdev_hgcm_function_parameter flags; + + /** + * value32, in/out: + * Bytes to be used for information/How many bytes were used. + */ + struct vmmdev_hgcm_function_parameter cb; + + /** + * pointer, in/out: + * Information to be set/get (shfl_fsobjinfo or shfl_string). Do not + * forget to set the shfl_fsobjinfo::attr::additional for a get + * operation as well. + */ + struct vmmdev_hgcm_function_parameter info; + +}; + +/* Number of parameters */ +#define SHFL_CPARMS_INFORMATION (5) + + +/* SHFL_FN_REMOVE */ + +#define SHFL_REMOVE_FILE (0x1) +#define SHFL_REMOVE_DIR (0x2) +#define SHFL_REMOVE_SYMLINK (0x4) + +/** SHFL_FN_REMOVE Parameters structure. */ +struct shfl_remove { + /** + * pointer, in: SHFLROOT (u32) + * Root handle of the mapping which name is queried. + */ + struct vmmdev_hgcm_function_parameter root; + + /** + * pointer, in: + * Points to struct shfl_string buffer. + */ + struct vmmdev_hgcm_function_parameter path; + + /** + * value32, in: + * remove flags (file/directory) + */ + struct vmmdev_hgcm_function_parameter flags; + +}; + +#define SHFL_CPARMS_REMOVE (3) + + +/* SHFL_FN_RENAME */ + +#define SHFL_RENAME_FILE (0x1) +#define SHFL_RENAME_DIR (0x2) +#define SHFL_RENAME_REPLACE_IF_EXISTS (0x4) + +/** SHFL_FN_RENAME Parameters structure. */ +struct shfl_rename { + /** + * pointer, in: SHFLROOT (u32) + * Root handle of the mapping which name is queried. + */ + struct vmmdev_hgcm_function_parameter root; + + /** + * pointer, in: + * Points to struct shfl_string src. + */ + struct vmmdev_hgcm_function_parameter src; + + /** + * pointer, in: + * Points to struct shfl_string dest. + */ + struct vmmdev_hgcm_function_parameter dest; + + /** + * value32, in: + * rename flags (file/directory) + */ + struct vmmdev_hgcm_function_parameter flags; + +}; + +#define SHFL_CPARMS_RENAME (4) + + +/** SHFL_FN_SYMLINK Parameters structure. */ +struct shfl_symlink { + /** + * pointer, in: SHFLROOT (u32) + * Root handle of the mapping which name is queried. + */ + struct vmmdev_hgcm_function_parameter root; + + /** + * pointer, in: + * Points to struct shfl_string of path for the new symlink. + */ + struct vmmdev_hgcm_function_parameter new_path; + + /** + * pointer, in: + * Points to struct shfl_string of destination for symlink. + */ + struct vmmdev_hgcm_function_parameter old_path; + + /** + * pointer, out: + * Information about created symlink. + */ + struct vmmdev_hgcm_function_parameter info; + +}; + +#define SHFL_CPARMS_SYMLINK (4) + +#endif diff --git a/fs/vboxsf/super.c b/fs/vboxsf/super.c new file mode 100644 index 000000000000..0de37cb56f29 --- /dev/null +++ b/fs/vboxsf/super.c @@ -0,0 +1,430 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * VirtualBox Guest Shared Folders support: Virtual File System. + * + * Module initialization/finalization + * File system registration/deregistration + * Superblock reading + * Few utility functions + * + * Copyright (C) 2006-2016 Oracle Corporation + */ + +#include <linux/magic.h> +#include <linux/module.h> +#include <linux/nls.h> +#include <linux/statfs.h> +#include <linux/vbox_utils.h> +#include <linux/vbsfmount.h> +#include "vfsmod.h" + +MODULE_DESCRIPTION("Oracle VM VirtualBox Module for Host File System Access"); +MODULE_AUTHOR("Oracle Corporation"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS_FS("vboxsf"); + +static struct super_operations sf_super_ops; /* forward declaration */ +static struct kmem_cache *sf_inode_cachep; + +/* allocate global info, try to map host share */ +static int sf_glob_alloc(struct vbsf_mount_info_new *info, + struct sf_glob_info **sf_gp) +{ + size_t name_len, str_len; + struct sf_glob_info *sf_g; + struct shfl_string *str_name = NULL; +#ifdef CONFIG_NLS_DEFAULT + const char *nls_name = CONFIG_NLS_DEFAULT; +#else + const char *nls_name = ""; +#endif + int err; + + if (info->nullchar != '\0' || + info->signature[0] != VBSF_MOUNT_SIGNATURE_BYTE_0 || + info->signature[1] != VBSF_MOUNT_SIGNATURE_BYTE_1 || + info->signature[2] != VBSF_MOUNT_SIGNATURE_BYTE_2) + return -EINVAL; + + sf_g = kzalloc(sizeof(*sf_g), GFP_KERNEL); + if (!sf_g) + return -ENOMEM; + + info->name[sizeof(info->name) - 1] = 0; + info->nls_name[sizeof(info->nls_name) - 1] = 0; + + name_len = strlen(info->name); + str_len = offsetof(struct shfl_string, string.utf8) + name_len + 1; + + str_name = kmalloc(str_len, GFP_KERNEL); + if (!str_name) { + err = -ENOMEM; + goto fail; + } + + str_name->length = name_len; + str_name->size = name_len + 1; + memcpy(str_name->string.utf8, info->name, name_len + 1); + + if (info->nls_name[0]) + nls_name = info->nls_name; + + /* Load nls if not utf8 */ + if (nls_name[0] && strcmp(nls_name, "utf8") != 0) { + sf_g->nls = load_nls(info->nls_name); + if (!sf_g->nls) { + err = -EINVAL; + goto fail; + } + } else { + sf_g->nls = NULL; + } + + err = vboxsf_map_folder(str_name, &sf_g->root); + if (err) + goto fail; + + kfree(str_name); + + sf_g->ttl = info->ttl; + sf_g->uid = info->uid; + sf_g->gid = info->gid; + + if ((size_t)info->length >= sizeof(struct vbsf_mount_info_new)) { + /* new fields */ + sf_g->dmode = info->dmode; + sf_g->fmode = info->fmode; + sf_g->dmask = info->dmask; + sf_g->fmask = info->fmask; + } else { + sf_g->dmode = ~0; + sf_g->fmode = ~0; + } + + *sf_gp = sf_g; + return 0; + +fail: + if (sf_g->nls) + unload_nls(sf_g->nls); + + kfree(str_name); + kfree(sf_g); + + return err; +} + +/* unmap the share and free global info [sf_g] */ +static void sf_glob_free(struct sf_glob_info *sf_g) +{ + vboxsf_unmap_folder(sf_g->root); + + if (sf_g->nls) + unload_nls(sf_g->nls); + + kfree(sf_g); +} + +/* + * This is called when vfs mounts the fs and wants to read the super_block. + * + * calls [sf_glob_alloc] to map the folder and allocate global + * information structure. + * + * initializes [sb], initializes root inode and dentry. + * + * should respect [flags] + */ +static int sf_read_super(struct super_block *sb, void *data, int flags) +{ + struct vbsf_mount_info_new *info = data; + struct shfl_string *path = NULL; + struct shfl_fsobjinfo fsinfo; + struct sf_glob_info *sf_g; + struct dentry *droot; + struct inode *iroot; + int err; + + if (!info) + return -EINVAL; + + if (flags & MS_REMOUNT) + return -EINVAL; + + err = sf_glob_alloc(info, &sf_g); + if (err) + return err; + + path = kmalloc(SHFLSTRING_HEADER_SIZE + 2, GFP_KERNEL); + if (!path) { + err = -ENOMEM; + goto fail; + } + + path->length = 1; + path->size = 2; + path->string.utf8[0] = '/'; + path->string.utf8[1] = 0; + + err = sf_stat(__func__, sf_g, path, &fsinfo, 0); + if (err) + goto fail; + + sb->s_magic = 0xface; + sb->s_blocksize = 1024; + sb->s_maxbytes = MAX_LFS_FILESIZE; + sb->s_op = &sf_super_ops; + + iroot = iget_locked(sb, 0); + if (!iroot) { + err = -ENOMEM; + goto fail; + } + + GET_INODE_INFO(iroot)->path = path; + sf_init_inode(sf_g, iroot, &fsinfo); + unlock_new_inode(iroot); + + droot = d_make_root(iroot); + if (!droot) { + err = -ENOMEM; + goto fail; + } + + sb->s_root = droot; + SET_GLOB_INFO(sb, sf_g); + return 0; + +fail: + kfree(path); + sf_glob_free(sf_g); + return err; +} + +static void sf_inode_init_once(void *data) +{ + struct sf_inode_info *sf_i = (struct sf_inode_info *)data; + + inode_init_once(&sf_i->vfs_inode); +} + +static struct inode *sf_alloc_inode(struct super_block *sb) +{ + struct sf_inode_info *sf_i; + + sf_i = kmem_cache_alloc(sf_inode_cachep, GFP_NOFS); + if (!sf_i) + return NULL; + + sf_i->path = NULL; + sf_i->force_restat = 0; + sf_i->force_reread = 0; + sf_i->file = NULL; + sf_i->handle = SHFL_HANDLE_NIL; + + return &sf_i->vfs_inode; +} + +static void sf_i_callback(struct rcu_head *head) +{ + struct inode *inode = container_of(head, struct inode, i_rcu); + kmem_cache_free(sf_inode_cachep, GET_INODE_INFO(inode)); +} + +static void sf_destroy_inode(struct inode *inode) +{ + call_rcu(&inode->i_rcu, sf_i_callback); +} + +/* + * This is called when vfs is about to destroy the [inode]. all + * resources associated with this [inode] must be cleared here. + */ +static void sf_evict_inode(struct inode *inode) +{ + truncate_inode_pages_final(&inode->i_data); + clear_inode(inode); + kfree(GET_INODE_INFO(inode)->path); +} + +/* + * vfs is done with [sb] (umount called) call [sf_glob_free] to unmap + * the folder and free [sf_g] + */ +static void sf_put_super(struct super_block *sb) +{ + struct sf_glob_info *sf_g; + + sf_g = GET_GLOB_INFO(sb); + sf_glob_free(sf_g); +} + +static int sf_statfs(struct dentry *dentry, struct kstatfs *stat) +{ + struct super_block *sb = dentry->d_inode->i_sb; + struct sf_glob_info *sf_g; + struct shfl_volinfo SHFLVolumeInfo; + u32 buf_len; + int err; + + sf_g = GET_GLOB_INFO(sb); + buf_len = sizeof(SHFLVolumeInfo); + err = vboxsf_fsinfo(sf_g->root, 0, SHFL_INFO_GET | SHFL_INFO_VOLUME, + &buf_len, &SHFLVolumeInfo); + if (err) + return err; + + stat->f_type = NFS_SUPER_MAGIC; /* XXX vboxsf type? */ + stat->f_bsize = SHFLVolumeInfo.bytes_per_allocation_unit; + + do_div(SHFLVolumeInfo.total_allocation_bytes, + SHFLVolumeInfo.bytes_per_allocation_unit); + stat->f_blocks = SHFLVolumeInfo.total_allocation_bytes; + + do_div(SHFLVolumeInfo.available_allocation_bytes, + SHFLVolumeInfo.bytes_per_allocation_unit); + stat->f_bfree = SHFLVolumeInfo.available_allocation_bytes; + stat->f_bavail = SHFLVolumeInfo.available_allocation_bytes; + + stat->f_files = 1000; + /* + * Don't return 0 here since the guest may then think that it is not + * possible to create any more files. + */ + stat->f_ffree = 1000; + stat->f_fsid.val[0] = 0; + stat->f_fsid.val[1] = 0; + stat->f_namelen = 255; + return 0; +} + +static int sf_remount_fs(struct super_block *sb, int *flags, char *data) +{ + struct sf_glob_info *sf_g; + struct sf_inode_info *sf_i; + struct inode *iroot; + struct shfl_fsobjinfo fsinfo; + int err; + + sf_g = GET_GLOB_INFO(sb); + if (data && data[0] != 0) { + struct vbsf_mount_info_new *info = + (struct vbsf_mount_info_new *)data; + if (info->signature[0] == VBSF_MOUNT_SIGNATURE_BYTE_0 + && info->signature[1] == VBSF_MOUNT_SIGNATURE_BYTE_1 + && info->signature[2] == VBSF_MOUNT_SIGNATURE_BYTE_2) { + sf_g->uid = info->uid; + sf_g->gid = info->gid; + sf_g->ttl = info->ttl; + sf_g->dmode = info->dmode; + sf_g->fmode = info->fmode; + sf_g->dmask = info->dmask; + sf_g->fmask = info->fmask; + } + } + + iroot = ilookup(sb, 0); + if (!iroot) + return -ENOENT; + + sf_i = GET_INODE_INFO(iroot); + err = sf_stat(__func__, sf_g, sf_i->path, &fsinfo, 0); + if (err == 0) + sf_init_inode(sf_g, iroot, &fsinfo); + else + vbg_warn("Error statting root fs after remount: %d\n", err); + + return 0; +} + +static struct super_operations sf_super_ops = { + .alloc_inode = sf_alloc_inode, + .destroy_inode = sf_destroy_inode, + .evict_inode = sf_evict_inode, + .put_super = sf_put_super, + .statfs = sf_statfs, + .remount_fs = sf_remount_fs +}; + +static struct dentry *sf_mount(struct file_system_type *fs_type, int flags, + const char *dev_name, void *data) +{ + return mount_nodev(fs_type, flags, data, sf_read_super); +} + +static struct file_system_type vboxsf_fs_type = { + .owner = THIS_MODULE, + .name = "vboxsf", + .mount = sf_mount, + .kill_sb = kill_anon_super +}; + +static int follow_symlinks; +module_param(follow_symlinks, int, 0444); +MODULE_PARM_DESC(follow_symlinks, + "Let host resolve symlinks rather than showing them"); + +/* Module initialization/finalization handlers */ +static int __init init(void) +{ + int err; + + if (sizeof(struct vbsf_mount_info_new) > PAGE_SIZE) { + vbg_err("vboxsf: Mount information structure is too large %zd; Must be less than or equal to %ld\n", + sizeof(struct vbsf_mount_info_new), PAGE_SIZE); + return -EINVAL; + } + + sf_inode_cachep = kmem_cache_create("vboxsf_inode_cache", + sizeof(struct sf_inode_info), + 0, (SLAB_RECLAIM_ACCOUNT| + SLAB_MEM_SPREAD|SLAB_ACCOUNT), + sf_inode_init_once); + if (sf_inode_cachep == NULL) + return -ENOMEM; + + err = register_filesystem(&vboxsf_fs_type); + if (err) + return err; + + err = vboxsf_connect(); + if (err) { + vbg_err("vboxsf_connect error %d\n", err); + goto fail_unregisterfs; + } + + err = vboxsf_set_utf8(); + if (err) { + vbg_err("vboxsf_setutf8 error %d\n", err); + goto fail_disconnect; + } + + if (!follow_symlinks) { + err = vboxsf_set_symlinks(); + if (err) + vbg_warn("vboxsf: Unable to show symlinks: %d\n", err); + } + + return 0; + +fail_disconnect: + vboxsf_disconnect(); +fail_unregisterfs: + unregister_filesystem(&vboxsf_fs_type); + return err; +} + +static void __exit fini(void) +{ + vboxsf_disconnect(); + unregister_filesystem(&vboxsf_fs_type); + /* + * Make sure all delayed rcu free inodes are flushed before we + * destroy cache. + */ + rcu_barrier(); + kmem_cache_destroy(sf_inode_cachep); +} + +module_init(init); +module_exit(fini); diff --git a/fs/vboxsf/utils.c b/fs/vboxsf/utils.c new file mode 100644 index 000000000000..9d457d0ac001 --- /dev/null +++ b/fs/vboxsf/utils.c @@ -0,0 +1,589 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * VirtualBox Guest Shared Folders support: Utility functions. + * Mainly conversion from/to VirtualBox/Linux data structures. + * + * Copyright (C) 2006-2016 Oracle Corporation + */ + +#include <linux/namei.h> +#include <linux/nls.h> +#include <linux/sizes.h> +#include <linux/vfs.h> +#include "vfsmod.h" + +/* + * sf_reg_aops and sf_backing_dev_info are just quick implementations to make + * sendfile work. For more information have a look at + * + * http://us1.samba.org/samba/ftp/cifs-cvs/ols2006-fs-tutorial-smf.odp + * + * and the sample implementation + * + * http://pserver.samba.org/samba/ftp/cifs-cvs/samplefs.tar.gz + */ + +static void sf_timespec_from_vbox(struct timespec *tv, + const struct shfl_timespec *ts) +{ + s64 nsec, t = ts->ns_relative_to_unix_epoch; + + nsec = do_div(t, 1000000000); + tv->tv_sec = t; + tv->tv_nsec = nsec; +} + +static void sf_timespec_to_vbox(struct shfl_timespec *ts, + const struct timespec *tv) +{ + s64 t = (s64) tv->tv_nsec + (s64) tv->tv_sec * 1000000000; + + ts->ns_relative_to_unix_epoch = t; +} + +/* set [inode] attributes based on [info], uid/gid based on [sf_g] */ +void sf_init_inode(struct sf_glob_info *sf_g, 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(ISUID); + mode |= mode_set(ISGID); + + 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 + + inode->i_mapping->a_ops = &sf_reg_aops; + + if (SHFL_IS_DIRECTORY(attr->mode)) { + inode->i_mode = sf_g->dmode != ~0 ? (sf_g->dmode & 0777) : mode; + inode->i_mode &= ~sf_g->dmask; + inode->i_mode |= S_IFDIR; + inode->i_op = &sf_dir_iops; + inode->i_fop = &sf_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 = sf_g->fmode != ~0 ? (sf_g->fmode & 0777) : mode; + inode->i_mode &= ~sf_g->fmask; + inode->i_mode |= S_IFLNK; + inode->i_op = &sf_lnk_iops; + set_nlink(inode, 1); + } else { + inode->i_mode = sf_g->fmode != ~0 ? (sf_g->fmode & 0777) : mode; + inode->i_mode &= ~sf_g->fmask; + inode->i_mode |= S_IFREG; + inode->i_op = &sf_reg_iops; + inode->i_fop = &sf_reg_fops; + set_nlink(inode, 1); + } + + inode->i_uid = make_kuid(current_user_ns(), sf_g->uid); + inode->i_gid = make_kgid(current_user_ns(), sf_g->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; + + sf_timespec_from_vbox(&inode->i_atime, &info->access_time); + sf_timespec_from_vbox(&inode->i_ctime, &info->change_time); + sf_timespec_from_vbox(&inode->i_mtime, &info->modification_time); +} + +int sf_stat(const char *caller, struct sf_glob_info *sf_g, + struct shfl_string *path, struct shfl_fsobjinfo *result, + int ok_to_fail) +{ + 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(sf_g->root, path, ¶ms); + if (err) + return err; + + if (params.result != SHFL_FILE_EXISTS) + return -ENOENT; + + *result = params.info; + return 0; +} + +/* + * This is called indirectly as iop through [sf_getattr]. The job is to find + * out whether dentry/inode is still valid. the test is failed if [dentry] + * does not have an inode or [sf_stat] is unsuccessful, otherwise we return + * success and update inode attributes. + */ +int sf_inode_revalidate(struct dentry *dentry) +{ + int err; + struct sf_glob_info *sf_g; + struct sf_inode_info *sf_i; + struct shfl_fsobjinfo info; + + if (!dentry || !dentry->d_inode) + return -EINVAL; + + sf_g = GET_GLOB_INFO(dentry->d_inode->i_sb); + sf_i = GET_INODE_INFO(dentry->d_inode); + + if (!sf_i->force_restat) { + if (time_before(jiffies, dentry->d_time + sf_g->ttl)) + return 0; + } + + err = sf_stat(__func__, sf_g, sf_i->path, &info, 1); + if (err) + return err; + + dentry->d_time = jiffies; + sf_i->force_restat = 0; + sf_init_inode(sf_g, dentry->d_inode, &info); + return 0; +} + +int sf_getattr(const struct path *path, struct kstat *kstat, u32 request_mask, + unsigned int flags) +{ + int err; + struct dentry *dentry = path->dentry; + + err = sf_inode_revalidate(dentry); + if (err) + return err; + + generic_fillattr(dentry->d_inode, kstat); + return 0; +} + +int sf_setattr(struct dentry *dentry, struct iattr *iattr) +{ + struct sf_glob_info *sf_g; + struct sf_inode_info *sf_i; + struct shfl_createparms params = {}; + struct shfl_fsobjinfo info = {}; + uint32_t buf_len; + int err; + + sf_g = GET_GLOB_INFO(dentry->d_inode->i_sb); + sf_i = GET_INODE_INFO(dentry->d_inode); + + 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(sf_g->root, sf_i->path, ¶ms); + if (err) + return err; + + if (params.result != SHFL_FILE_EXISTS) + return -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(ISUID); + info.attr.mode |= mode_set(ISGID); + 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) + sf_timespec_to_vbox(&info.access_time, + &iattr->ia_atime); + + if (iattr->ia_valid & ATTR_MTIME) + sf_timespec_to_vbox(&info.modification_time, + &iattr->ia_mtime); + + /* + * Ignore ctime (inode change time) as it can't be set + * from userland anyway. + */ + + buf_len = sizeof(info); + err = vboxsf_fsinfo(sf_g->root, params.handle, + SHFL_INFO_SET | SHFL_INFO_FILE, &buf_len, + &info); + if (err) + goto out; + } + +#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(sf_g->root, params.handle, + SHFL_INFO_SET | SHFL_INFO_SIZE, &buf_len, + &info); + if (err) + goto out; + } + + err = sf_inode_revalidate(dentry); +out: + vboxsf_close(sf_g->root, params.handle); + return err; +} + +static int sf_make_path(const char *caller, struct sf_inode_info *sf_i, + const char *d_name, size_t d_len, + struct shfl_string **result) +{ + size_t path_len, shflstring_len; + struct shfl_string *tmp; + uint16_t p_len; + uint8_t *p_name; + int fRoot = 0; + + p_len = sf_i->path->length; + p_name = sf_i->path->string.utf8; + + if (p_len == 1 && *p_name == '/') { + path_len = d_len + 1; + fRoot = 1; + } else { + /* lengths of constituents plus terminating zero plus slash */ + path_len = p_len + d_len + 2; + if (path_len > 0xffff) + return -ENAMETOOLONG; + } + + shflstring_len = offsetof(struct shfl_string, string.utf8) + path_len; + tmp = kmalloc(shflstring_len, GFP_KERNEL); + if (!tmp) + return -ENOMEM; + + tmp->length = path_len - 1; + tmp->size = path_len; + + if (fRoot) + memcpy(&tmp->string.utf8[0], d_name, d_len + 1); + else { + memcpy(&tmp->string.utf8[0], p_name, p_len); + tmp->string.utf8[p_len] = '/'; + memcpy(&tmp->string.utf8[p_len + 1], d_name, d_len); + tmp->string.utf8[p_len + 1 + d_len] = '\0'; + } + + *result = tmp; + return 0; +} + +/* + * [dentry] contains string encoded in coding system that corresponds + * to [sf_g]->nls, we must convert it to UTF8 here and pass down to + * [sf_make_path] which will allocate struct shfl_string and fill it in + */ +int sf_path_from_dentry(const char *caller, struct sf_glob_info *sf_g, + struct sf_inode_info *sf_i, struct dentry *dentry, + struct shfl_string **result) +{ + int err; + const char *d_name; + size_t d_len; + const char *name; + size_t len = 0; + + d_name = dentry->d_name.name; + d_len = dentry->d_name.len; + + if (sf_g->nls) { + size_t in_len, i, out_bound_len; + const char *in; + char *out; + + in = d_name; + in_len = d_len; + + out_bound_len = PATH_MAX; + out = kmalloc(out_bound_len, GFP_KERNEL); + name = out; + + for (i = 0; i < d_len; ++i) { + wchar_t uni; + int nb; + + nb = sf_g->nls->char2uni(in, in_len, &uni); + if (nb < 0) { + err = -EINVAL; + goto fail; + } + in_len -= nb; + in += nb; + + nb = utf32_to_utf8(uni, out, out_bound_len); + if (nb < 0) { + err = -EINVAL; + goto fail; + } + out_bound_len -= nb; + out += nb; + len += nb; + } + if (len >= PATH_MAX - 1) { + err = -ENAMETOOLONG; + goto fail; + } + + *out = 0; + } else { + name = d_name; + len = d_len; + } + + err = sf_make_path(caller, sf_i, name, len, result); + if (name != d_name) + kfree(name); + + return err; + +fail: + kfree(name); + return err; +} + +int sf_nlscpy(struct sf_glob_info *sf_g, + char *name, size_t name_bound_len, + const unsigned char *utf8_name, size_t utf8_len) +{ + if (sf_g->nls) { + 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; + out_bound_len = name_bound_len; + + 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 = sf_g->nls->uni2char(uni, out, out_bound_len); + if (nb < 0) + return nb; + + out += nb; + out_bound_len -= nb; + out_len += nb; + } + + *out = 0; + } else { + if (utf8_len + 1 > name_bound_len) + return -ENAMETOOLONG; + + memcpy(name, utf8_name, utf8_len + 1); + } + return 0; +} + +static struct sf_dir_buf *sf_dir_buf_alloc(void) +{ + struct sf_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; + } + + INIT_LIST_HEAD(&b->head); + b->entries = 0; + b->used = 0; + b->free = DIR_BUFFER_SIZE; + + return b; +} + +static void sf_dir_buf_free(struct sf_dir_buf *b) +{ + list_del(&b->head); + kfree(b->buf); + kfree(b); +} + +/** + * Free the directory buffer. + * @p sf_dir_info buffer to free + */ +void sf_dir_info_free(struct sf_dir_info *p) +{ + struct list_head *list, *pos, *tmp; + + list = &p->info_list; + list_for_each_safe(pos, tmp, list) { + struct sf_dir_buf *b; + + b = list_entry(pos, struct sf_dir_buf, head); + sf_dir_buf_free(b); + } + kfree(p); +} + +/** + * Empty (but not free) the directory buffer. + * @p sf_dir_info buffer to empty + */ +void sf_dir_info_empty(struct sf_dir_info *p) +{ + struct list_head *list, *pos, *tmp; + struct sf_dir_buf *b; + + list = &p->info_list; + list_for_each_safe(pos, tmp, list) { + b = list_entry(pos, struct sf_dir_buf, head); + b->entries = 0; + b->used = 0; + b->free = DIR_BUFFER_SIZE; + } +} + +/** + * Create a new directory buffer descriptor. + * Return: Created sf_dir_info buffer, or NULL when malloc fails + */ +struct sf_dir_info *sf_dir_info_alloc(void) +{ + struct sf_dir_info *p; + + p = kmalloc(sizeof(*p), GFP_KERNEL); + if (!p) + return NULL; + + INIT_LIST_HEAD(&p->info_list); + return p; +} + +/* Search for an empty directory content buffer. */ +static struct sf_dir_buf *sf_get_empty_dir_buf(struct sf_dir_info *sf_d) +{ + struct list_head *list, *pos; + + list = &sf_d->info_list; + list_for_each(pos, list) { + struct sf_dir_buf *b; + + b = list_entry(pos, struct sf_dir_buf, head); + if (!b) + return NULL; + + if (b->used == 0) + return b; + } + + return NULL; +} + +int sf_dir_read_all(struct sf_glob_info *sf_g, struct sf_inode_info *sf_i, + struct sf_dir_info *sf_d, u64 handle) +{ + struct shfl_string *mask; + struct sf_dir_buf *b; + u32 entries, size; + void *buf; + int err; + + err = sf_make_path(__func__, sf_i, "*", 1, &mask); + if (err) + return err; + + /* vboxsf_dirinfo returns 1 on end of dir */ + while (err == 0) { + b = sf_get_empty_dir_buf(sf_d); + if (!b) { + b = sf_dir_buf_alloc(); + if (!b) { + err = -ENOMEM; + break; + } + list_add(&b->head, &sf_d->info_list); + } + + buf = b->buf; + size = b->free; + + err = vboxsf_dirinfo(sf_g->root, handle, mask, 0, 0, + &size, buf, &entries); + if (err < 0) + break; + + b->entries += entries; + b->free -= size; + b->used += size; + } + + kfree(mask); + + /* -EILSEQ means the host could not translate a filename, ignore */ + if (err > 0 || err == -EILSEQ) + err = 0; + + return err; +} diff --git a/fs/vboxsf/vboxsf_wrappers.c b/fs/vboxsf/vboxsf_wrappers.c new file mode 100644 index 000000000000..3781ac466609 --- /dev/null +++ b/fs/vboxsf/vboxsf_wrappers.c @@ -0,0 +1,365 @@ +// SPDX-License-Identifier: (GPL-2.0 OR CDDL-1.0) +/* + * Wrapper functions for the shfl host calls. + * + * Copyright (C) 2006-2016 Oracle Corporation + */ + +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/vbox_err.h> +#include <linux/vbox_utils.h> +#include "vboxsf_wrappers.h" + +static u32 vboxsf_client_id; + +int vboxsf_connect(void) +{ + struct vbg_dev *gdev; + struct vmmdev_hgcm_service_location loc; + int err, vbox_status; + + loc.type = VMMDEV_HGCM_LOC_LOCALHOST_EXISTING; + strcpy(loc.u.localhost.service_name, "VBoxSharedFolders"); + + gdev = vbg_get_gdev(); + if (IS_ERR(gdev)) + return VERR_NOT_SUPPORTED; /* No guest-device */ + + err = vbg_hgcm_connect(gdev, &loc, &vboxsf_client_id, &vbox_status); + vbg_put_gdev(gdev); + + return err ? err : vbg_status_code_to_errno(vbox_status); +} + +void vboxsf_disconnect(void) +{ + struct vbg_dev *gdev; + int vbox_status; + + gdev = vbg_get_gdev(); + if (IS_ERR(gdev)) + return; /* guest-device is gone, already disconnected */ + + vbg_hgcm_disconnect(gdev, vboxsf_client_id, &vbox_status); + vbg_put_gdev(gdev); +} + +static int vboxsf_call(u32 function, void *parms, u32 parm_count, int *status) +{ + struct vbg_dev *gdev; + int err, vbox_status; + + gdev = vbg_get_gdev(); + if (IS_ERR(gdev)) + return VERR_DEV_IO_ERROR; /* guest-dev removed underneath us */ + + err = vbg_hgcm_call(gdev, vboxsf_client_id, function, U32_MAX, + parms, parm_count, &vbox_status); + vbg_put_gdev(gdev); + + if (err < 0) + return err; + + if (status) + *status = vbox_status; + + return vbg_status_code_to_errno(vbox_status); +} + +int vboxsf_map_folder(struct shfl_string *folder_name, u32 *root) +{ + struct shfl_map_folder parms; + int err, status; + + parms.path.type = VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL; + parms.path.u.pointer.size = shfl_string_buf_size(folder_name); + parms.path.u.pointer.u.linear_addr = (uintptr_t)folder_name; + + parms.root.type = VMMDEV_HGCM_PARM_TYPE_32BIT; + parms.root.u.value32 = 0; + + parms.delimiter.type = VMMDEV_HGCM_PARM_TYPE_32BIT; + parms.delimiter.u.value32 = '/'; + + parms.case_sensitive.type = VMMDEV_HGCM_PARM_TYPE_32BIT; + parms.case_sensitive.u.value32 = 1; + + err = vboxsf_call(SHFL_FN_MAP_FOLDER, &parms, SHFL_CPARMS_MAP_FOLDER, + &status); + if (err == -ENOSYS && status == VERR_NOT_IMPLEMENTED) + vbg_err("%s: Error host is too old\n", __func__); + + *root = parms.root.u.value32; + return err; +} + +int vboxsf_unmap_folder(u32 root) +{ + struct shfl_unmap_folder parms; + + parms.root.type = VMMDEV_HGCM_PARM_TYPE_32BIT; + parms.root.u.value32 = root; + + return vboxsf_call(SHFL_FN_UNMAP_FOLDER, &parms, + SHFL_CPARMS_UNMAP_FOLDER, NULL); +} + +/** + * Create a new file or folder or open an existing one in a shared folder. + * Note this function always returns 0 / success unless an exceptional condition + * occurs - out of memory, invalid arguments, etc. If the file or folder could + * not be opened or created, create_parms->handle will be set to + * SHFL_HANDLE_NIL on return. In this case the value in create_parms->result + * provides information as to why (e.g. SHFL_FILE_EXISTS), create_parms->result + * is also set on success as additional information. + * Return: 0 or negative errno value. + * @root Root of the shared folder in which to create the file + * @parsed_path The path of the file or folder relative to the shared folder + * @param create_parms Parameters for file/folder creation. + */ +int vboxsf_create(u32 root, struct shfl_string *parsed_path, + struct shfl_createparms *create_parms) +{ + struct shfl_create parms; + + parms.root.type = VMMDEV_HGCM_PARM_TYPE_32BIT; + parms.root.u.value32 = root; + + parms.path.type = VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL; + parms.path.u.pointer.size = shfl_string_buf_size(parsed_path); + parms.path.u.pointer.u.linear_addr = (uintptr_t)parsed_path; + + parms.parms.type = VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL; + parms.parms.u.pointer.size = sizeof(struct shfl_createparms); + parms.parms.u.pointer.u.linear_addr = (uintptr_t)create_parms; + + return vboxsf_call(SHFL_FN_CREATE, &parms, SHFL_CPARMS_CREATE, NULL); +} + +int vboxsf_close(u32 root, u64 handle) +{ + struct shfl_close parms; + + 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; + + return vboxsf_call(SHFL_FN_CLOSE, &parms, SHFL_CPARMS_CLOSE, NULL); +} + +int vboxsf_remove(u32 root, struct shfl_string *parsed_path, u32 flags) +{ + struct shfl_remove parms; + + parms.root.type = VMMDEV_HGCM_PARM_TYPE_32BIT; + parms.root.u.value32 = root; + + parms.path.type = VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_IN; + parms.path.u.pointer.size = shfl_string_buf_size(parsed_path); + parms.path.u.pointer.u.linear_addr = (uintptr_t)parsed_path; + + parms.flags.type = VMMDEV_HGCM_PARM_TYPE_32BIT; + parms.flags.u.value32 = flags; + + return vboxsf_call(SHFL_FN_REMOVE, &parms, SHFL_CPARMS_REMOVE, NULL); +} + +int vboxsf_rename(u32 root, struct shfl_string *src_path, + struct shfl_string *dest_path, u32 flags) +{ + struct shfl_rename parms; + + parms.root.type = VMMDEV_HGCM_PARM_TYPE_32BIT; + parms.root.u.value32 = root; + + parms.src.type = VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_IN; + parms.src.u.pointer.size = shfl_string_buf_size(src_path); + parms.src.u.pointer.u.linear_addr = (uintptr_t)src_path; + + parms.dest.type = VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_IN; + parms.dest.u.pointer.size = shfl_string_buf_size(dest_path); + parms.dest.u.pointer.u.linear_addr = (uintptr_t)dest_path; + + parms.flags.type = VMMDEV_HGCM_PARM_TYPE_32BIT; + parms.flags.u.value32 = flags; + + return vboxsf_call(SHFL_FN_RENAME, &parms, SHFL_CPARMS_RENAME, NULL); +} + +int vboxsf_read(u32 root, u64 handle, u64 offset, + u32 *buf_len, u8 *buf, bool user) +{ + struct shfl_read 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_OUT; + else + parms.buffer.type = VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_OUT; + parms.buffer.u.pointer.size = *buf_len; + parms.buffer.u.pointer.u.linear_addr = (uintptr_t)buf; + + err = vboxsf_call(SHFL_FN_READ, &parms, SHFL_CPARMS_READ, NULL); + + *buf_len = parms.cb.u.value32; + return err; +} + +int vboxsf_write(u32 root, u64 handle, u64 offset, + u32 *buf_len, const u8 *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 = (uintptr_t)buf; + + err = vboxsf_call(SHFL_FN_WRITE, &parms, SHFL_CPARMS_WRITE, NULL); + + *buf_len = parms.cb.u.value32; + return err; +} + +/* Returns 0 on success, 1 on end-of-dir, negative errno otherwise */ +int vboxsf_dirinfo(u32 root, u64 handle, + struct shfl_string *parsed_path, u32 flags, u32 index, + u32 *buf_len, struct shfl_dirinfo *buf, u32 *file_count) +{ + struct shfl_list parms; + int err, status; + + 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.flags.type = VMMDEV_HGCM_PARM_TYPE_32BIT; + parms.flags.u.value32 = flags; + parms.cb.type = VMMDEV_HGCM_PARM_TYPE_32BIT; + parms.cb.u.value32 = *buf_len; + parms.path.type = VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_IN; + parms.path.u.pointer.size = + parsed_path ? shfl_string_buf_size(parsed_path) : 0; + parms.path.u.pointer.u.linear_addr = (uintptr_t)parsed_path; + + parms.buffer.type = VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_OUT; + parms.buffer.u.pointer.size = *buf_len; + parms.buffer.u.pointer.u.linear_addr = (uintptr_t)buf; + + parms.resume_point.type = VMMDEV_HGCM_PARM_TYPE_32BIT; + parms.resume_point.u.value32 = index; + parms.file_count.type = VMMDEV_HGCM_PARM_TYPE_32BIT; + parms.file_count.u.value32 = 0; /* out parameter only */ + + err = vboxsf_call(SHFL_FN_LIST, &parms, SHFL_CPARMS_LIST, &status); + if (err == -ENODATA && status == VERR_NO_MORE_FILES) + err = 1; + + *buf_len = parms.cb.u.value32; + *file_count = parms.file_count.u.value32; + return err; +} + +int vboxsf_fsinfo(u32 root, u64 handle, u32 flags, + u32 *buf_len, void *buf) +{ + struct shfl_information 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.flags.type = VMMDEV_HGCM_PARM_TYPE_32BIT; + parms.flags.u.value32 = flags; + parms.cb.type = VMMDEV_HGCM_PARM_TYPE_32BIT; + parms.cb.u.value32 = *buf_len; + parms.info.type = VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL; + parms.info.u.pointer.size = *buf_len; + parms.info.u.pointer.u.linear_addr = (uintptr_t)buf; + + err = vboxsf_call(SHFL_FN_INFORMATION, &parms, SHFL_CPARMS_INFORMATION, + NULL); + + *buf_len = parms.cb.u.value32; + return err; +} + +int vboxsf_readlink(u32 root, struct shfl_string *parsed_path, + u32 buf_len, u8 *buf) +{ + struct shfl_readLink parms; + + parms.root.type = VMMDEV_HGCM_PARM_TYPE_32BIT; + parms.root.u.value32 = root; + + parms.path.type = VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_IN; + parms.path.u.pointer.size = shfl_string_buf_size(parsed_path); + parms.path.u.pointer.u.linear_addr = (uintptr_t)parsed_path; + + parms.buffer.type = VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_OUT; + parms.buffer.u.pointer.size = buf_len; + parms.buffer.u.pointer.u.linear_addr = (uintptr_t)buf; + + return vboxsf_call(SHFL_FN_READLINK, &parms, SHFL_CPARMS_READLINK, + NULL); +} + +int vboxsf_symlink(u32 root, struct shfl_string *new_path, + struct shfl_string *old_path, struct shfl_fsobjinfo *buf) +{ + struct shfl_symlink parms; + + parms.root.type = VMMDEV_HGCM_PARM_TYPE_32BIT; + parms.root.u.value32 = root; + + parms.new_path.type = VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_IN; + parms.new_path.u.pointer.size = shfl_string_buf_size(new_path); + parms.new_path.u.pointer.u.linear_addr = (uintptr_t)new_path; + + parms.old_path.type = VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_IN; + parms.old_path.u.pointer.size = shfl_string_buf_size(old_path); + parms.old_path.u.pointer.u.linear_addr = (uintptr_t)old_path; + + parms.info.type = VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_OUT; + parms.info.u.pointer.size = sizeof(struct shfl_fsobjinfo); + parms.info.u.pointer.u.linear_addr = (uintptr_t)buf; + + return vboxsf_call(SHFL_FN_SYMLINK, &parms, SHFL_CPARMS_SYMLINK, NULL); +} + +int vboxsf_set_utf8(void) +{ + return vboxsf_call(SHFL_FN_SET_UTF8, NULL, 0, NULL); +} + +int vboxsf_set_symlinks(void) +{ + return vboxsf_call(SHFL_FN_SET_SYMLINKS, NULL, 0, NULL); +} diff --git a/fs/vboxsf/vboxsf_wrappers.h b/fs/vboxsf/vboxsf_wrappers.h new file mode 100644 index 000000000000..9abca9aea6a9 --- /dev/null +++ b/fs/vboxsf/vboxsf_wrappers.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR CDDL-1.0) */ +/* + * Protype declarations for the wrapper functions for the shfl host calls. + * + * Copyright (C) 2006-2016 Oracle Corporation + */ + +#ifndef VBOXSF_WRAPPERS_H +#define VBOXSF_WRAPPERS_H + +#include "shfl_hostintf.h" + +int vboxsf_connect(void); +void vboxsf_disconnect(void); + +int vboxsf_create(u32 root, struct shfl_string *parsed_path, + struct shfl_createparms *create_parms); + +int vboxsf_close(u32 root, u64 handle); +int vboxsf_remove(u32 root, struct shfl_string *parsed_path, u32 flags); +int vboxsf_rename(u32 root, struct shfl_string *src_path, + struct shfl_string *dest_path, u32 flags); + +int vboxsf_read(u32 root, u64 handle, u64 offset, + u32 *buf_len, u8 *buf, bool user); +int vboxsf_write(u32 root, u64 handle, u64 offset, + u32 *buf_len, const u8 *buf, bool user); + +int vboxsf_dirinfo(u32 root, u64 handle, + struct shfl_string *parsed_path, u32 flags, u32 index, + u32 *buf_len, struct shfl_dirinfo *buf, u32 *file_count); +int vboxsf_fsinfo(u32 root, u64 handle, u32 flags, + u32 *buf_len, void *buf); + +int vboxsf_map_folder(struct shfl_string *folder_name, u32 *root); +int vboxsf_unmap_folder(u32 root); + +int vboxsf_readlink(u32 root, struct shfl_string *parsed_path, + u32 buf_len, u8 *buf); +int vboxsf_symlink(u32 root, struct shfl_string *new_path, + struct shfl_string *old_path, struct shfl_fsobjinfo *buf); + +int vboxsf_set_utf8(void); +int vboxsf_set_symlinks(void); + +#endif diff --git a/fs/vboxsf/vfsmod.h b/fs/vboxsf/vfsmod.h new file mode 100644 index 000000000000..29dd69aacfdc --- /dev/null +++ b/fs/vboxsf/vfsmod.h @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * VirtualBox Guest Shared Folders support: module header. + * + * Copyright (C) 2006-2016 Oracle Corporation + */ + +#ifndef VFSMOD_H +#define VFSMOD_H + +#include <linux/backing-dev.h> +#include <linux/version.h> +#include "vboxsf_wrappers.h" + +#define DIR_BUFFER_SIZE SZ_16K + +/* The cast is to prevent assignment of void * to pointers of arbitrary type */ +#define GET_GLOB_INFO(sb) ((struct sf_glob_info *)(sb)->s_fs_info) +#define SET_GLOB_INFO(sb, sf_g) ((sb)->s_fs_info = (sf_g)) +#define GET_INODE_INFO(i) container_of(i, struct sf_inode_info, vfs_inode) +#define GET_F_DENTRY(f) (f->f_path.dentry) + +/* per-shared folder information */ +struct sf_glob_info { + u32 root; + struct nls_table *nls; + int ttl; + int uid; + int gid; + int dmode; + int fmode; + int dmask; + int fmask; +}; + +/* per-inode information */ +struct sf_inode_info { + /* which file */ + struct shfl_string *path; + /* some information was changed, update data on next revalidate */ + int force_restat; + /* + * directory content changed, update the whole directory on next + * sf_getdent + */ + int force_reread; + /* file structure, only valid between open() and release() */ + struct file *file; + /* + * handle valid if a file was created with sf_create_aux until it + * will be opened with sf_reg_open() + */ + u64 handle; + /* The VFS inode struct */ + struct inode vfs_inode; +}; + +struct sf_dir_info { + struct list_head info_list; +}; + +struct sf_dir_buf { + size_t entries; + size_t free; + size_t used; + void *buf; + struct list_head head; +}; + +struct sf_reg_info { + u64 handle; +}; + +/* globals */ +extern const struct inode_operations sf_dir_iops; +extern const struct inode_operations sf_lnk_iops; +extern const struct inode_operations sf_reg_iops; +extern const struct file_operations sf_dir_fops; +extern const struct file_operations sf_reg_fops; +extern const struct address_space_operations sf_reg_aops; + +void sf_init_inode(struct sf_glob_info *sf_g, struct inode *inode, + const struct shfl_fsobjinfo *info); +int sf_stat(const char *caller, struct sf_glob_info *sf_g, + struct shfl_string *path, struct shfl_fsobjinfo *result, + int ok_to_fail); +int sf_inode_revalidate(struct dentry *dentry); +int sf_getattr(const struct path *path, struct kstat *kstat, + u32 request_mask, unsigned int query_flags); +int sf_setattr(struct dentry *dentry, struct iattr *iattr); +int sf_path_from_dentry(const char *caller, struct sf_glob_info *sf_g, + struct sf_inode_info *sf_i, + struct dentry *dentry, struct shfl_string **result); +int sf_nlscpy(struct sf_glob_info *sf_g, char *name, + size_t name_bound_len, const unsigned char *utf8_name, + size_t utf8_len); +void sf_dir_info_free(struct sf_dir_info *p); +void sf_dir_info_empty(struct sf_dir_info *p); +struct sf_dir_info *sf_dir_info_alloc(void); +int sf_dir_read_all(struct sf_glob_info *sf_g, + struct sf_inode_info *sf_i, struct sf_dir_info *sf_d, + u64 handle); + +#endif diff --git a/include/uapi/linux/vbsfmount.h b/include/uapi/linux/vbsfmount.h new file mode 100644 index 000000000000..21aa546a2a64 --- /dev/null +++ b/include/uapi/linux/vbsfmount.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2006-2016 Oracle Corporation + * + * VirtualBox Guest Shared Folders: mount(2) parameter structure. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef VBFS_MOUNT_H +#define VBFS_MOUNT_H + +/* Linux constraints the size of data mount argument to PAGE_SIZE - 1. */ +#define MAX_HOST_NAME 256 +#define MAX_NLS_NAME 32 + +#define VBSF_MOUNT_SIGNATURE_BYTE_0 ('\377') +#define VBSF_MOUNT_SIGNATURE_BYTE_1 ('\376') +#define VBSF_MOUNT_SIGNATURE_BYTE_2 ('\375') + +struct vbsf_mount_info_new { + /* + * The old version of the mount_info struct started with a + * char name[MAX_HOST_NAME] field, where name cannot be '\0'. + * So the new version of the mount_info struct starts with a + * nullchar field which is always 0 so that we can detect and + * reject the old structure being passed. + */ + char nullchar; + char signature[3]; /* signature */ + int length; /* length of the whole structure */ + char name[MAX_HOST_NAME]; /* share name */ + char nls_name[MAX_NLS_NAME]; /* name of an I/O charset */ + int uid; /* user ID for all entries, default 0=root */ + int gid; /* group ID for all entries, default 0=root */ + int ttl; /* time to live */ + int dmode; /* mode for directories if != 0xffffffff */ + int fmode; /* mode for regular files if != 0xffffffff */ + int dmask; /* umask applied to directories */ + int fmask; /* umask applied to regular files */ +}; + +struct vbsf_mount_opts { + int uid; + int gid; + int ttl; + int dmode; + int fmode; + int dmask; + int fmask; + int ronly; + int sloppy; + int noexec; + int nodev; + int nosuid; + int remount; + char nls_name[MAX_NLS_NAME]; + char *convertcp; +}; + +#endif /* vbsfmount.h */ -- 2.14.3