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> --- MAINTAINERS | 6 + fs/Kconfig | 1 + fs/Makefile | 1 + fs/vboxsf/Kconfig | 9 + fs/vboxsf/Makefile | 3 + fs/vboxsf/dir.c | 575 +++++++++++++++++++++++++++ fs/vboxsf/file.c | 414 ++++++++++++++++++++ fs/vboxsf/shfl_hostintf.h | 919 ++++++++++++++++++++++++++++++++++++++++++++ fs/vboxsf/super.c | 429 +++++++++++++++++++++ fs/vboxsf/utils.c | 536 ++++++++++++++++++++++++++ fs/vboxsf/vboxsf_wrappers.c | 370 ++++++++++++++++++ fs/vboxsf/vfsmod.h | 133 +++++++ 12 files changed, 3396 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/vfsmod.h diff --git a/MAINTAINERS b/MAINTAINERS index 1cba18567fce..380d04ff6db1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14585,6 +14585,12 @@ F: include/linux/vbox_utils.h F: include/uapi/linux/vbox*.h F: drivers/virt/vboxguest/ +VIRTUAL BOX SHARED FOLDER VFS DRIVER: +M: Hans de Goede <hdegoede@xxxxxxxxxx> +L: linux-fsdevel@xxxxxxxxxxxxxxx +S: Maintained +F: fs/vboxsf/* + VIRTUAL SERIO DEVICE DRIVER M: Stephen Chandler Paul <thatslyude@xxxxxxxxx> S: Maintained 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..390d6fc5bef0 --- /dev/null +++ b/fs/vboxsf/dir.c @@ -0,0 +1,575 @@ +// 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) +{ + struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb); + struct shfl_createparms params = {}; + struct sf_dir_info *sf_d; + int err; + + sf_d = vboxsf_dir_info_alloc(); + if (!sf_d) + return -ENOMEM; + + params.handle = SHFL_HANDLE_NIL; + params.create_flags = 0 + | SHFL_CF_DIRECTORY + | SHFL_CF_ACT_OPEN_IF_EXISTS + | SHFL_CF_ACT_FAIL_IF_NEW | SHFL_CF_ACCESS_READ; + + err = vboxsf_create_at_dentry(file_dentry(file), ¶ms); + if (err == 0) { + if (params.result == SHFL_FILE_EXISTS) { + err = vboxsf_dir_read_all(sf_g, sf_d, params.handle); + if (!err) + file->private_data = sf_d; + } else + err = -ENOENT; + + vboxsf_close(sf_g->root, params.handle); + } + + if (err) + vboxsf_dir_info_free(sf_d); + + return err; +} + +/** + * This is called when reference count of [file] goes to zero. Notify + * the host that it can free whatever is associated with this directory + * and deallocate our own internal buffers + * Return: 0 or negative errno value. + * @inode inode + * @file file + */ +static int sf_dir_release(struct inode *inode, struct file *file) +{ + if (file->private_data) + vboxsf_dir_info_free(file->private_data); + + return 0; +} + +/** + * Translate RTFMODE into DT_xxx (in conjunction to rtDirType()) + * Return: d_type + * @mode file mode + */ +static int sf_get_d_type(u32 mode) +{ + int d_type; + + switch (mode & SHFL_TYPE_MASK) { + case SHFL_TYPE_FIFO: + d_type = DT_FIFO; + break; + case SHFL_TYPE_DEV_CHAR: + d_type = DT_CHR; + break; + case SHFL_TYPE_DIRECTORY: + d_type = DT_DIR; + break; + case SHFL_TYPE_DEV_BLOCK: + d_type = DT_BLK; + break; + case SHFL_TYPE_FILE: + d_type = DT_REG; + break; + case SHFL_TYPE_SYMLINK: + d_type = DT_LNK; + break; + case SHFL_TYPE_SOCKET: + d_type = DT_SOCK; + break; + case SHFL_TYPE_WHITEOUT: + d_type = DT_WHT; + break; + default: + d_type = DT_UNKNOWN; + break; + } + return d_type; +} + +/** + * Extract element ([dir]->f_pos) from the directory [dir] into [d_name]. + * Return: 0 or negative errno value. + * @dir Directory to get element at f_pos from + * @d_name Buffer in which to return element name + * @d_type Buffer in which to return element file-type + */ +static int sf_getdent(struct file *dir, char d_name[NAME_MAX], int *d_type) +{ + struct sf_glob_info *sf_g = GET_GLOB_INFO(file_inode(dir)->i_sb); + struct sf_dir_info *sf_d = dir->private_data; + struct list_head *pos; + loff_t cur = 0; + + list_for_each(pos, &sf_d->info_list) { + struct shfl_dirinfo *info; + struct sf_dir_buf *b; + loff_t i; + + b = list_entry(pos, struct sf_dir_buf, head); + 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 vboxsf_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 vboxsf_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 (d_really_is_positive(dentry)) + return vboxsf_inode_revalidate(dentry) == 0; + else + return vboxsf_stat_dentry(dentry, NULL) == -ENOENT; +} + +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 inode *inode; + ino_t ino; + int err; + + sf_g = GET_GLOB_INFO(parent->i_sb); + sf_i = GET_INODE_INFO(parent); + + err = vboxsf_stat_dentry(dentry, &fsinfo); + if (err) { + if (err != -ENOENT) + return ERR_PTR(err); + /* + * -ENOENT: add NULL inode to dentry so it later can + * be created via call to create/mkdir/open + */ + inode = NULL; + } else { + ino = iunique(parent->i_sb, 1); + inode = iget_locked(parent->i_sb, ino); + if (!inode) + return ERR_PTR(-ENOMEM); + + vboxsf_init_inode(sf_g, inode, &fsinfo); + unlock_new_inode(inode); + } + + dentry->d_time = jiffies; + d_set_d_op(dentry, &sf_dentry_ops); + d_add(dentry, inode); + 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 + * @info file information + * @handle handle + */ +static int sf_instantiate(struct inode *parent, struct dentry *dentry, + struct shfl_fsobjinfo *info, u64 handle) +{ + struct sf_glob_info *sf_g = GET_GLOB_INFO(parent->i_sb); + struct sf_inode_info *sf_i; + struct inode *inode; + ino_t ino; + + ino = iunique(parent->i_sb, 1); + inode = iget_locked(parent->i_sb, ino); + if (!inode) { + if (handle != SHFL_HANDLE_NIL) + vboxsf_close(sf_g->root, handle); + return -ENOMEM; + } + + sf_i = GET_INODE_INFO(inode); + /* the host may have given us different attr then requested */ + sf_i->force_restat = 1; + sf_i->handle = handle; + vboxsf_init_inode(sf_g, inode, info); + + d_instantiate(dentry, inode); + unlock_new_inode(inode); + + 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) +{ + struct sf_inode_info *sf_parent_i = GET_INODE_INFO(parent); + struct sf_glob_info *sf_g = GET_GLOB_INFO(parent->i_sb); + struct shfl_createparms params = {}; + int 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_at_dentry(dentry, ¶ms); + if (err) + return err; + + if (params.result != SHFL_FILE_CREATED) + return -EPERM; + + if (is_dir) { + vboxsf_close(sf_g->root, params.handle); + params.handle = SHFL_HANDLE_NIL; + } + + err = sf_instantiate(parent, dentry, ¶ms.info, params.handle); + if (err) + return err; + + /* parent directory access/change time changed */ + sf_parent_i->force_restat = 1; + + /* + * We leave the handle open. We assume that the same file is opened + * with sf_reg_open() and later closed with sf_reg_close(). + */ + return 0; +} + +/** + * 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) +{ + struct sf_glob_info *sf_g = GET_GLOB_INFO(parent->i_sb); + struct sf_inode_info *sf_parent_i = GET_INODE_INFO(parent); + struct inode *inode = d_inode(dentry); + struct shfl_string *path; + uint32_t flags; + int err; + + flags = is_dir ? SHFL_REMOVE_DIR : SHFL_REMOVE_FILE; + if (inode && (inode->i_mode & S_IFLNK) == S_IFLNK) + flags |= SHFL_REMOVE_SYMLINK; + + path = vboxsf_path_from_dentry(sf_g, dentry); + if (IS_ERR(path)) + return PTR_ERR(path); + + err = vboxsf_remove(sf_g->root, path, flags); + __putname(path); + if (err) + return err; + + /* parent directory access/change time changed */ + sf_parent_i->force_restat = 1; + + return 0; +} + +/** + * Remove a regular file. + * Return: 0 or negative errno value. + * @parent inode of the directory + * @dentry directory cache entry + */ +static int sf_unlink(struct inode *parent, struct dentry *dentry) +{ + return sf_unlink_aux(parent, dentry, 0); +} + +/** + * Remove a directory. + * Return: 0 or negative errno value. + * @parent inode of the directory + * @dentry directory cache entry + */ +static int sf_rmdir(struct inode *parent, struct dentry *dentry) +{ + return sf_unlink_aux(parent, dentry, 1); +} + +/** + * Rename a regular file / directory. + * Return: 0 or negative errno value. + * @old_parent inode of the old parent directory + * @old_dentry old directory cache entry + * @new_parent inode of the new parent directory + * @new_dentry new directory cache entry + * @flags flags + */ +static int sf_rename(struct inode *old_parent, struct dentry *old_dentry, + struct inode *new_parent, struct dentry *new_dentry, + unsigned int flags) +{ + struct sf_glob_info *sf_g = GET_GLOB_INFO(old_parent->i_sb); + struct sf_inode_info *sf_old_parent_i = GET_INODE_INFO(old_parent); + struct sf_inode_info *sf_new_parent_i = GET_INODE_INFO(new_parent); + u32 shfl_flags = SHFL_RENAME_FILE | SHFL_RENAME_REPLACE_IF_EXISTS; + struct shfl_string *old_path, *new_path; + int err; + + if (flags) + return -EINVAL; + + if (sf_g != GET_GLOB_INFO(new_parent->i_sb)) + return -EINVAL; + + old_path = vboxsf_path_from_dentry(sf_g, old_dentry); + if (IS_ERR(old_path)) + return PTR_ERR(old_path); + + new_path = vboxsf_path_from_dentry(sf_g, new_dentry); + if (IS_ERR(new_path)) { + __putname(old_path); + return PTR_ERR(new_path); + } + + if (d_inode(old_dentry)->i_mode & S_IFDIR) + shfl_flags = 0; + + err = vboxsf_rename(sf_g->root, old_path, new_path, shfl_flags); + if (err == 0) { + /* parent directories access/change time changed */ + sf_new_parent_i->force_restat = 1; + sf_old_parent_i->force_restat = 1; + } + + __putname(new_path); + __putname(old_path); + return err; +} + +static int sf_symlink(struct inode *parent, struct dentry *dentry, + const char *symname) +{ + struct sf_inode_info *sf_parent_i = GET_INODE_INFO(parent); + struct sf_glob_info *sf_g = GET_GLOB_INFO(parent->i_sb); + int symname_size = strlen(symname) + 1; + struct shfl_string *path, *ssymname; + struct shfl_fsobjinfo info; + int err; + + path = vboxsf_path_from_dentry(sf_g, dentry); + if (IS_ERR(path)) + return PTR_ERR(path); + + ssymname = kmalloc(SHFLSTRING_HEADER_SIZE + symname_size, GFP_KERNEL); + if (!ssymname) { + __putname(path); + return -ENOMEM; + } + ssymname->length = symname_size - 1; + ssymname->size = symname_size; + memcpy(ssymname->string.utf8, symname, symname_size); + + err = vboxsf_symlink(sf_g->root, path, ssymname, &info); + kfree(ssymname); + __putname(path); + if (err) + return err; + + err = sf_instantiate(parent, dentry, &info, SHFL_HANDLE_NIL); + if (err) + return err; + + /* parent directory access/change time changed */ + sf_parent_i->force_restat = 1; + return 0; +} + +const struct inode_operations vboxsf_dir_iops = { + .lookup = sf_lookup, + .create = sf_create, + .mkdir = sf_mkdir, + .rmdir = sf_rmdir, + .unlink = sf_unlink, + .rename = sf_rename, + .getattr = vboxsf_getattr, + .setattr = vboxsf_setattr, + .symlink = sf_symlink +}; diff --git a/fs/vboxsf/file.c b/fs/vboxsf/file.c new file mode 100644 index 000000000000..7123e2dea4db --- /dev/null +++ b/fs/vboxsf/file.c @@ -0,0 +1,414 @@ +// 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 sf_glob_info *sf_g = GET_GLOB_INFO(file_inode(file)->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 = file_inode(file); + struct sf_inode_info *sf_i = GET_INODE_INFO(inode); + struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb); + struct sf_reg_info *sf_r = file->private_data; + u32 nwritten; + u64 pos; + int err; + + pos = *off; + if (file->f_flags & O_APPEND) { + pos = inode->i_size; + *off = pos; + } + + 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); + + /* size changed */ + 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_inode_info *sf_i = GET_INODE_INFO(inode); + struct shfl_createparms params = {}; + struct sf_reg_info *sf_r; + int err; + + sf_r = kmalloc(sizeof(*sf_r), GFP_KERNEL); + if (!sf_r) + return -ENOMEM; + + /* Already open? */ + if (sf_i->handle != SHFL_HANDLE_NIL) { + sf_r->handle = sf_i->handle; + sf_i->handle = SHFL_HANDLE_NIL; + sf_i->file = file; + file->private_data = sf_r; + return 0; + } + + /* + * 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. + */ + params.handle = SHFL_HANDLE_NIL; + 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_at_dentry(file_dentry(file), ¶ms); + if (err == 0 && params.handle == SHFL_HANDLE_NIL) + err = (params.result == SHFL_FILE_EXISTS) ? -EEXIST : -ENOENT; + if (err) { + kfree(sf_r); + return err; + } + + /* the host may have given us different attr then requested */ + 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 sf_glob_info *sf_g = GET_GLOB_INFO(file_inode(file)->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 vboxsf_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 vboxsf_reg_iops = { + .getattr = vboxsf_getattr, + .setattr = vboxsf_setattr +}; + +static int sf_readpage(struct file *file, struct page *page) +{ + struct sf_glob_info *sf_g = GET_GLOB_INFO(file_inode(file)->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 nwrite = PAGE_SIZE; + u8 *buf; + int err; + + if (page->index >= end_index) + nwrite = inode->i_size & (PAGE_SIZE - 1); + + buf = kmap(page); + err = vboxsf_write(sf_g->root, sf_r->handle, off, &nwrite, 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 vboxsf_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 shfl_string *path; + char *link; + int err; + + if (!dentry) + return ERR_PTR(-ECHILD); + + path = vboxsf_path_from_dentry(sf_g, dentry); + if (IS_ERR(path)) + return (char *)path; + + link = kzalloc(PATH_MAX, GFP_KERNEL); + if (!link) { + __putname(path); + return ERR_PTR(-ENOMEM); + } + + err = vboxsf_readlink(sf_g->root, path, PATH_MAX, link); + __putname(path); + if (err) { + kfree(link); + return ERR_PTR(err); + } + + set_delayed_call(done, kfree_link, link); + return link; +} + +const struct inode_operations vboxsf_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..a26aac9d319b --- /dev/null +++ b/fs/vboxsf/super.c @@ -0,0 +1,429 @@ +// 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/parser.h> +#include <linux/statfs.h> +#include <linux/vbox_utils.h> +#include "vfsmod.h" + +#define VBSF_MOUNT_SIGNATURE_BYTE_0 ('\000') +#define VBSF_MOUNT_SIGNATURE_BYTE_1 ('\377') +#define VBSF_MOUNT_SIGNATURE_BYTE_2 ('\376') +#define VBSF_MOUNT_SIGNATURE_BYTE_3 ('\375') + +static int follow_symlinks; +module_param(follow_symlinks, int, 0444); +MODULE_PARM_DESC(follow_symlinks, + "Let host resolve symlinks rather than showing them"); + +static struct super_operations sf_super_ops; /* forward declaration */ +static struct kmem_cache *sf_inode_cachep; + +char * const vboxsf_default_nls = "utf8"; + +enum { opt_name, opt_nls, opt_uid, opt_gid, opt_ttl, opt_dmode, opt_fmode, + opt_dmask, opt_fmask, opt_error }; + +static const match_table_t vboxsf_tokens = { + { opt_name, "name=%s" }, + { opt_nls, "nls=%" }, + { opt_uid, "uid=%u" }, + { opt_gid, "gid=%u" }, + { opt_ttl, "ttl=%u" }, + { opt_dmode, "dmode=%o" }, + { opt_fmode, "fmode=%o" }, + { opt_dmask, "dmask=%o" }, + { opt_fmask, "fmask=%o" }, + { opt_error, NULL }, +}; + +static int vboxsf_parse_options(struct sf_glob_info *sf_g, char *options) +{ + substring_t args[MAX_OPT_ARGS]; + int value, token; + size_t size; + char *p; + + if (!options) + goto out; + + if (options[0] == VBSF_MOUNT_SIGNATURE_BYTE_0 && + options[1] == VBSF_MOUNT_SIGNATURE_BYTE_1 && + options[2] == VBSF_MOUNT_SIGNATURE_BYTE_2 && + options[3] == VBSF_MOUNT_SIGNATURE_BYTE_3) { + vbg_err("vboxsf: Old binary mount data not supported, remove obsolete mount.vboxsf and/or update your VBoxService.\n"); + return -EINVAL; + } + + while ((p = strsep(&options, ",")) != NULL) { + if (!*p) + continue; + + token = match_token(p, vboxsf_tokens, args); + switch (token) { + case opt_name: + if (sf_g->name) { + vbg_err("vboxsf: Cannot change mount name\n"); + return -EINVAL; + } + size = args[0].to - args[0].from + 1; + sf_g->name = kmalloc(SHFLSTRING_HEADER_SIZE + size, + GFP_KERNEL); + if (!sf_g->name) + return -ENOMEM; + sf_g->name->size = size; + sf_g->name->length = size - 1; + match_strlcpy(sf_g->name->string.utf8, &args[0], size); + break; + case opt_nls: + if (sf_g->nls_name) { + vbg_err("vboxsf: Cannot change nls option\n"); + return -EINVAL; + } + sf_g->nls_name = match_strdup(&args[0]); + if (!sf_g->nls_name) + return -ENOMEM; + break; + case opt_uid: + if (match_int(&args[0], &value)) + return -EINVAL; + sf_g->uid = value; + break; + case opt_gid: + if (match_int(&args[0], &value)) + return -EINVAL; + sf_g->gid = value; + break; + case opt_ttl: + if (match_int(&args[0], &value)) + return -EINVAL; + sf_g->ttl = msecs_to_jiffies(value); + break; + case opt_dmode: + if (match_octal(&args[0], &value)) + return -EINVAL; + sf_g->dmode = value; + break; + case opt_fmode: + if (match_octal(&args[0], &value)) + return -EINVAL; + sf_g->fmode = value; + break; + case opt_dmask: + if (match_octal(&args[0], &value)) + return -EINVAL; + sf_g->dmask = value; + break; + case opt_fmask: + if (match_octal(&args[0], &value)) + return -EINVAL; + sf_g->fmask = value; + break; + default: + vbg_err("vboxsf: Unrecognized mount option \"%s\" or missing value\n", + p); + return -EINVAL; + } + } + +out: + if (!sf_g->name) { + vbg_err("vboxsf: mount name option is mandatory\n"); + return -EINVAL; + } + + if (!sf_g->nls_name) + sf_g->nls_name = vboxsf_default_nls; + + return 0; +} + +/* + * Called when vfs mounts the fs, should respect [flags], + * initializes [sb], initializes root inode and dentry. + */ +static int sf_read_super(struct super_block *sb, void *data, int flags) +{ + struct shfl_string root_path; + struct sf_glob_info *sf_g; + struct dentry *droot; + struct inode *iroot; + int err; + + if (flags & MS_REMOUNT) + return -EINVAL; + + sf_g = kzalloc(sizeof(*sf_g), GFP_KERNEL); + if (!sf_g) + return -ENOMEM; + + /* ~0 means use whatever the host gives as mode info */ + sf_g->dmode = ~0; + sf_g->fmode = ~0; + + err = vboxsf_parse_options(sf_g, data); + if (err) + goto fail_free; + + /* Load nls if not utf8 */ + if (strcmp(sf_g->nls_name, "utf8") != 0) { + sf_g->nls = load_nls(sf_g->nls_name); + if (!sf_g->nls) { + err = -EINVAL; + goto fail_free; + } + } + + err = vboxsf_map_folder(sf_g->name, &sf_g->root); + if (err) + goto fail_free; + + root_path.length = 1; + root_path.size = 2; + strlcpy(root_path.string.utf8, "/", + sizeof(root_path) - SHFLSTRING_HEADER_SIZE); + err = vboxsf_stat(sf_g, &root_path, &sf_g->root_info); + if (err) + goto fail_unmap; + + 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_unmap; + } + vboxsf_init_inode(sf_g, iroot, &sf_g->root_info); + unlock_new_inode(iroot); + + droot = d_make_root(iroot); + if (!droot) { + err = -ENOMEM; + goto fail_unmap; + } + + sb->s_root = droot; + SET_GLOB_INFO(sb, sf_g); + return 0; + +fail_unmap: + vboxsf_unmap_folder(sf_g->root); +fail_free: + if (sf_g->nls) + unload_nls(sf_g->nls); + if (sf_g->nls_name != vboxsf_default_nls) + kfree(sf_g->nls_name); + kfree(sf_g->name); + kfree(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->force_restat = 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); +} + +/* + * 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 = GET_GLOB_INFO(sb); + + vboxsf_unmap_folder(sf_g->root); + if (sf_g->nls) + unload_nls(sf_g->nls); + if (sf_g->nls_name != vboxsf_default_nls) + kfree(sf_g->nls_name); + kfree(sf_g->name); + kfree(sf_g); +} + +static int sf_statfs(struct dentry *dentry, struct kstatfs *stat) +{ + struct super_block *sb = dentry->d_sb; + struct shfl_volinfo SHFLVolumeInfo; + struct sf_glob_info *sf_g; + 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 *options) +{ + struct sf_glob_info *sf_g = GET_GLOB_INFO(sb); + struct inode *iroot; + int err; + + err = vboxsf_parse_options(sf_g, options); + if (err) + return err; + + iroot = ilookup(sb, 0); + if (!iroot) + return -ENOENT; + + /* Apply changed options to the root inode */ + vboxsf_init_inode(sf_g, iroot, &sf_g->root_info); + + return 0; +} + +static struct super_operations sf_super_ops = { + .alloc_inode = sf_alloc_inode, + .destroy_inode = sf_destroy_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 +}; + +/* Module initialization/finalization handlers */ +static int __init init(void) +{ + int err; + + 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); + +MODULE_DESCRIPTION("Oracle VM VirtualBox Module for Host File System Access"); +MODULE_AUTHOR("Oracle Corporation"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS_FS("vboxsf"); diff --git a/fs/vboxsf/utils.c b/fs/vboxsf/utils.c new file mode 100644 index 000000000000..9dbbc453ac7e --- /dev/null +++ b/fs/vboxsf/utils.c @@ -0,0 +1,536 @@ +// 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" + +/* + * vboxsf_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 vboxsf_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 = &vboxsf_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 = &vboxsf_dir_iops; + inode->i_fop = &vboxsf_dir_fops; + /* + * XXX: this probably should be set to the number of entries + * in the directory plus two (. ..) + */ + set_nlink(inode, 1); + } else if (SHFL_IS_SYMLINK(attr->mode)) { + inode->i_mode = sf_g->fmode != ~0 ? (sf_g->fmode & 0777) : mode; + inode->i_mode &= ~sf_g->fmask; + inode->i_mode |= S_IFLNK; + inode->i_op = &vboxsf_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 = &vboxsf_reg_iops; + inode->i_fop = &vboxsf_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 vboxsf_create_at_dentry(struct dentry *dentry, + struct shfl_createparms *params) +{ + struct sf_glob_info *sf_g = GET_GLOB_INFO(dentry->d_sb); + struct shfl_string *path; + int err; + + path = vboxsf_path_from_dentry(sf_g, dentry); + if (IS_ERR(path)) + return PTR_ERR(path); + + err = vboxsf_create(sf_g->root, path, params); + __putname(path); + + return err; +} + +int vboxsf_stat(struct sf_glob_info *sf_g, struct shfl_string *path, + struct shfl_fsobjinfo *info) +{ + struct shfl_createparms params = {}; + int err; + + params.handle = SHFL_HANDLE_NIL; + params.create_flags = SHFL_CF_LOOKUP | SHFL_CF_ACT_FAIL_IF_NEW; + + err = vboxsf_create(sf_g->root, path, ¶ms); + if (err) + return err; + + if (params.result != SHFL_FILE_EXISTS) + return -ENOENT; + + if (info) + *info = params.info; + + return 0; +} + +int vboxsf_stat_dentry(struct dentry *dentry, struct shfl_fsobjinfo *info) +{ + struct sf_glob_info *sf_g = GET_GLOB_INFO(dentry->d_sb); + struct shfl_string *path; + int err; + + path = vboxsf_path_from_dentry(sf_g, dentry); + if (IS_ERR(path)) + return PTR_ERR(path); + + err = vboxsf_stat(sf_g, path, info); + __putname(path); + return err; +} + +int vboxsf_inode_revalidate(struct dentry *dentry) +{ + struct sf_glob_info *sf_g = GET_GLOB_INFO(dentry->d_sb); + struct sf_inode_info *sf_i; + struct shfl_fsobjinfo info; + int err; + + if (!dentry || !d_really_is_positive(dentry)) + return -EINVAL; + + sf_i = GET_INODE_INFO(d_inode(dentry)); + if (!sf_i->force_restat) { + if (time_before(jiffies, dentry->d_time + sf_g->ttl)) + return 0; + } + + err = vboxsf_stat_dentry(dentry, &info); + if (err) + return err; + + dentry->d_time = jiffies; + sf_i->force_restat = 0; + vboxsf_init_inode(sf_g, d_inode(dentry), &info); + return 0; +} + +int vboxsf_getattr(const struct path *path, struct kstat *kstat, + u32 request_mask, unsigned int flags) +{ + int err; + struct dentry *dentry = path->dentry; + + err = vboxsf_inode_revalidate(dentry); + if (err) + return err; + + generic_fillattr(d_inode(dentry), kstat); + return 0; +} + +int vboxsf_setattr(struct dentry *dentry, struct iattr *iattr) +{ + struct sf_inode_info *sf_i = GET_INODE_INFO(d_inode(dentry)); + struct sf_glob_info *sf_g = GET_GLOB_INFO(dentry->d_sb); + struct shfl_createparms params = {}; + struct shfl_fsobjinfo info = {}; + uint32_t buf_len; + int err; + + params.handle = SHFL_HANDLE_NIL; + params.create_flags = SHFL_CF_ACT_OPEN_IF_EXISTS | + SHFL_CF_ACT_FAIL_IF_NEW | + SHFL_CF_ACCESS_ATTR_WRITE; + + /* this is at least required for Posix hosts */ + if (iattr->ia_valid & ATTR_SIZE) + params.create_flags |= SHFL_CF_ACCESS_WRITE; + + err = vboxsf_create_at_dentry(dentry, ¶ms); + if (err || params.result != SHFL_FILE_EXISTS) + return err ? err : -ENOENT; + +#define mode_set(r) ((iattr->ia_mode & (S_##r)) ? SHFL_UNIX_##r : 0) + + /* + * Setting the file size and setting the other attributes has to + * be handled separately. + */ + if (iattr->ia_valid & (ATTR_MODE | ATTR_ATIME | ATTR_MTIME)) { + if (iattr->ia_valid & ATTR_MODE) { + info.attr.mode = mode_set(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) { + vboxsf_close(sf_g->root, params.handle); + return err; + } + + /* the host may have given us different attr then requested */ + sf_i->force_restat = 1; + } + +#undef mode_set + + if (iattr->ia_valid & ATTR_SIZE) { + memset(&info, 0, sizeof(info)); + info.size = iattr->ia_size; + buf_len = sizeof(info); + err = vboxsf_fsinfo(sf_g->root, params.handle, + SHFL_INFO_SET | SHFL_INFO_SIZE, &buf_len, + &info); + if (err) { + vboxsf_close(sf_g->root, params.handle); + return err; + } + + /* the host may have given us different attr then requested */ + sf_i->force_restat = 1; + } + + vboxsf_close(sf_g->root, params.handle); + + /* Update the inode with what the host has actually given us. */ + if (sf_i->force_restat) + vboxsf_inode_revalidate(dentry); + + return 0; +} + +/* + * [dentry] contains string encoded in coding system that corresponds + * to [sf_g]->nls, we must convert it to UTF8 here. + * Returns a shfl_string allocated through __getname (must be freed using + * __putname), or an ERR_PTR on error. + */ +struct shfl_string *vboxsf_path_from_dentry(struct sf_glob_info *sf_g, + struct dentry *dentry) +{ + struct shfl_string *shfl_path; + int path_len, out_len, nb; + char *buf, *path; + wchar_t uni; + u8 *out; + + buf = __getname(); + if (!buf) + return ERR_PTR(-ENOMEM); + + path = dentry_path_raw(dentry, buf, PATH_MAX); + if (IS_ERR(path)) { + __putname(buf); + return (struct shfl_string *)path; + } + path_len = strlen(path); + + if (sf_g->nls) { + shfl_path = __getname(); + if (!shfl_path) { + __putname(buf); + return ERR_PTR(-ENOMEM); + } + + out = shfl_path->string.utf8; + out_len = PATH_MAX - SHFLSTRING_HEADER_SIZE - 1; + + while (path_len) { + nb = sf_g->nls->char2uni(path, path_len, &uni); + if (nb < 0) { + __putname(shfl_path); + __putname(buf); + return ERR_PTR(-EINVAL); + } + path += nb; + path_len -= nb; + + nb = utf32_to_utf8(uni, out, out_len); + if (nb < 0) { + __putname(shfl_path); + __putname(buf); + return ERR_PTR(-ENAMETOOLONG); + } + out += nb; + out_len -= nb; + } + *out = 0; + shfl_path->length = out - shfl_path->string.utf8; + shfl_path->size = shfl_path->length + 1; + __putname(buf); + } else { + if ((SHFLSTRING_HEADER_SIZE + path_len + 1) > PATH_MAX) { + __putname(buf); + return ERR_PTR(-ENAMETOOLONG); + } + /* + * dentry_path stores the name at the end of buf, but the + * shfl_string string we return must be properly aligned. + */ + shfl_path = (struct shfl_string *)buf; + memmove(shfl_path->string.utf8, path, path_len); + shfl_path->string.utf8[path_len] = 0; + shfl_path->length = path_len; + shfl_path->size = path_len + 1; + } + + return shfl_path; +} + +int vboxsf_nlscpy(struct 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(struct list_head *list) +{ + 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; + } + + b->entries = 0; + b->used = 0; + b->free = DIR_BUFFER_SIZE; + list_add(&b->head, list); + + return b; +} + +static void sf_dir_buf_free(struct sf_dir_buf *b) +{ + list_del(&b->head); + kfree(b->buf); + kfree(b); +} + +/** + * Create a new directory buffer descriptor. + * Return: Created sf_dir_info buffer, or NULL when malloc fails + */ +struct sf_dir_info *vboxsf_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; +} + +/** + * Free the directory buffer. + * @p sf_dir_info buffer to free + */ +void vboxsf_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); +} + +int vboxsf_dir_read_all(struct sf_glob_info *sf_g, struct sf_dir_info *sf_d, + u64 handle) +{ + struct sf_dir_buf *b; + u32 entries, size; + int err = 0; + void *buf; + + /* vboxsf_dirinfo returns 1 on end of dir */ + while (err == 0) { + b = sf_dir_buf_alloc(&sf_d->info_list); + if (!b) { + err = -ENOMEM; + break; + } + + buf = b->buf; + size = b->free; + + err = vboxsf_dirinfo(sf_g->root, handle, NULL, 0, 0, + &size, buf, &entries); + if (err < 0) + break; + + b->entries += entries; + b->free -= size; + b->used += size; + } + + if (b && b->used == 0) + sf_dir_buf_free(b); + + /* -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..a5ddcae38799 --- /dev/null +++ b/fs/vboxsf/vboxsf_wrappers.c @@ -0,0 +1,370 @@ +// 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 "vfsmod.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; + if (parsed_path) { + 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; + } else { + parms.path.type = VMMDEV_HGCM_PARM_TYPE_LINADDR_IN; + parms.path.u.pointer.size = 0; + parms.path.u.pointer.u.linear_addr = 0; + } + + 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/vfsmod.h b/fs/vboxsf/vfsmod.h new file mode 100644 index 000000000000..20efcb77232d --- /dev/null +++ b/fs/vboxsf/vfsmod.h @@ -0,0 +1,133 @@ +/* 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 "shfl_hostintf.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) + +/* per-shared folder information */ +struct sf_glob_info { + struct shfl_fsobjinfo root_info; + struct nls_table *nls; + u32 root; + /* mount options */ + struct shfl_string *name; + char *nls_name; + int ttl; + int uid; + int gid; + int dmode; + int fmode; + int dmask; + int fmask; +}; + +/* per-inode information */ +struct sf_inode_info { + /* some information was changed, update data on next revalidate */ + int force_restat; + /* 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 vboxsf_dir_iops; +extern const struct inode_operations vboxsf_lnk_iops; +extern const struct inode_operations vboxsf_reg_iops; +extern const struct file_operations vboxsf_dir_fops; +extern const struct file_operations vboxsf_reg_fops; +extern const struct address_space_operations vboxsf_reg_aops; + +/* from utils.c */ +void vboxsf_init_inode(struct sf_glob_info *sf_g, struct inode *inode, + const struct shfl_fsobjinfo *info); +int vboxsf_create_at_dentry(struct dentry *dentry, + struct shfl_createparms *params); +int vboxsf_stat(struct sf_glob_info *sf_g, struct shfl_string *path, + struct shfl_fsobjinfo *info); +int vboxsf_stat_dentry(struct dentry *dentry, struct shfl_fsobjinfo *info); +int vboxsf_inode_revalidate(struct dentry *dentry); +int vboxsf_getattr(const struct path *path, struct kstat *kstat, + u32 request_mask, unsigned int query_flags); +int vboxsf_setattr(struct dentry *dentry, struct iattr *iattr); +struct shfl_string *vboxsf_path_from_dentry(struct sf_glob_info *sf_g, + struct dentry *dentry); +int vboxsf_nlscpy(struct sf_glob_info *sf_g, char *name, size_t name_bound_len, + const unsigned char *utf8_name, size_t utf8_len); +struct sf_dir_info *vboxsf_dir_info_alloc(void); +void vboxsf_dir_info_free(struct sf_dir_info *p); +int vboxsf_dir_read_all(struct sf_glob_info *sf_g, struct sf_dir_info *sf_d, + u64 handle); + +/* from vboxsf_wrappers.c */ +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 -- 2.14.3