Just for completeness, here's the updated patch for vmblock, which I've run through the vmblocktest tool that Adar posted. Seems a shame not to post it somewhere so you guys might as well have it!
It's checkpatch and sparse clean, and applies against 2.6.27. -- From: Chris Malley <[EMAIL PROTECTED]> Subject: [PATCH] fs: add support for VMware vmblock "blocking" filesystem This is a pseudo filesystem used by VMware tools to enforce safe drag-and-drop / copy-and-paste functionality between host and guest. Adapted from the open-vm-tools project, version 2008.10.10 Signed-off-by: Chris Malley <[EMAIL PROTECTED]> --- Documentation/filesystems/vmblock.txt | 68 ++ fs/Kconfig | 21 + fs/Makefile | 1 + fs/vmblock/Makefile | 7 + fs/vmblock/vmblock_fs.c | 1159 +++++++++++++++++++++++++++++++++ include/linux/Kbuild | 1 + include/linux/vmblock.h | 49 ++ 7 files changed, 1305 insertions(+), 0 deletions(-) create mode 100644 Documentation/filesystems/vmblock.txt create mode 100644 fs/vmblock/Makefile create mode 100644 fs/vmblock/vmblock_fs.c create mode 100644 include/linux/vmblock.h diff --git a/Documentation/filesystems/vmblock.txt b/Documentation/filesystems/vmblock.txt new file mode 100644 index 0000000..ff5f98b --- /dev/null +++ b/Documentation/filesystems/vmblock.txt @@ -0,0 +1,68 @@ +VMware Blocking Filesystem driver +--------------------------------- + +PURPOSE + +VMware have created a driver to enable safe drag and drop operations +between host and guest, by implementing a pseudo "blocking" filesystem. + +The idea is that files can be copied via a temporary directory, but the target +should not be able to access the file until the copy has finished. This is +accomplished by mounting a "view" of the directory, containing only symlinks +to the real files, and a special control file. Before copying, a "block" +command is issued to the control file along with the filename. Now any attempt +to access the file via the view will block until a "release" command is issued +(presumably after the source is happy that the copy has completed). + + +TESTING + +1. Make a directory on the guest to store the files (/tmp/VMwareDnD by default): + # mkdir /tmp/VMwareDnD + # chmod 1777 /tmp/VMwareDnD + # echo "a test file" > /tmp/VMwareDnD/foo + +2. Load the vmblock module: + # modprobe vmblock + # ls -p /proc/fs/vmblock + dev mountPoint/ + +3. Mount the blocking filesystem: + # mount -t vmblock none /proc/fs/vmblock/mountPoint + # ls -l /proc/fs/vmblock/mountPoint + foo -> /tmp/VMwareDnD/foo + +4. To place a block on a file (see listing [1] below): + # cat /proc/fs/vmblock/mountPoint/foo + a test file + # addblock_wait10 /tmp/VMwareDnD/foo & + # cat /proc/fs/vmblock/mountPoint/foo + <ten seconds later....> + a test file + + +EXAMPLE USERSPACE CODE + +/* addblock_wait10.c */ +#include <linux/limits.h> +#include <linux/vmblock.h> +#include <string.h> +#include <fcntl.h> + +int main(int argc, char *argv[]) { + char buf[PATH_MAX]; + + int fd = open(VMBLOCK_DEVICE, VMBLOCK_DEVICE_MODE); + + strncpy(buf, argv[1], PATH_MAX); + /* + * The size of the write dictates the type of command. + * For example, VMBLOCK_ADD_FILEBLOCK is 98 bytes. + * However the module will actually read PATH_MAX bytes! + */ + write(fd, buf, VMBLOCK_ADD_FILEBLOCK); /* should return 0 */ + sleep(10); /* file is now blocked for 10 secs */ + + close(fd); /* block is released */ +} +/* end of addblock_wait10.c */ diff --git a/fs/Kconfig b/fs/Kconfig index abccb5d..cbf4837 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -979,6 +979,27 @@ config CONFIGFS_FS Both sysfs and configfs can and should exist together on the same system. One is not a replacement for the other. +config VMBLOCK_FS + tristate "VMware blocking filesystem (EXPERIMENTAL)" + depends on EXPERIMENTAL + default n + help + Select this option if you plan to run VMware and you want + to enable drag-and-drop and copy-and-paste operations between + host and guest. + + See <file:Documentation/filesystems/vmblock.txt> for details. + + If you have no intention of running VMware, say N. + +config VMBLOCK_DEBUG + bool "VMware blocking filesystem debugging" + default n + depends on VMBLOCK_FS + help + Enable vmblock debugging features such as the facility to list + all the current file blocks. + endmenu menu "Miscellaneous filesystems" diff --git a/fs/vmblock/Makefile b/fs/vmblock/Makefile new file mode 100644 index 0000000..5dfb4db --- /dev/null +++ b/fs/vmblock/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the linux vmblock filesystem routines. +# + +obj-$(CONFIG_VMBLOCK_FS) += vmblock.o + +vmblock-objs := vmblock_fs.o diff --git a/fs/vmblock/vmblock_fs.c b/fs/vmblock/vmblock_fs.c new file mode 100644 index 0000000..129d25a --- /dev/null +++ b/fs/vmblock/vmblock_fs.c @@ -0,0 +1,1159 @@ +/* + * Copyright (C) 2006 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 and no later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * Rework for inclusion into Linux mainline + * Copyright (C) 2008, Chris Malley <[EMAIL PROTECTED]> + */ + +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/namei.h> +#include <linux/module.h> +#include <linux/proc_fs.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/statfs.h> +#include <linux/string.h> +#include <linux/vmblock.h> + +#define VMBLOCK_FS_NAME "vmblock" +#define VMBLOCK_ROOT_INO 1 +#define VMBLOCK_BLOCKSIZE 1024 +#define VMBLOCK_SUPER_MAGIC 0xabababab +#define VMBLOCK_UNKNOWN_BLOCKER NULL + +#define VMBLOCK_ERROR(fmt, args...) \ + pr_err("vmblock:%s:%u " fmt "\n", __func__, __LINE__, ##args) +#define VMBLOCK_WARNING(fmt, args...) \ + pr_warning("vmblock:%s:%u " fmt "\n", __func__, __LINE__, ##args) +#define VMBLOCK_INFO(fmt, args...) \ + pr_info("vmblock:%s:%u " fmt "\n", __func__, __LINE__, ##args) +#define VMBLOCK_DEBUG(fmt, args...) \ + pr_debug("vmblock:%s:%u " fmt "\n", __func__, __LINE__, ##args) + + +struct vmb_inode_info { + char name[PATH_MAX]; + size_t name_len; + struct dentry *actual_dentry; + struct inode inode; +}; + +struct vmb_blocking_info { + struct list_head links; + atomic_t refcount; + struct file *blocker; + struct completion completion; + char filename[PATH_MAX]; +}; + +struct vmb_filldir_info { + filldir_t filldir; + void *dirent; +}; + +static struct kmem_cache *vmb_inode_info_cache; +static char *root = "/tmp/VMwareDnD"; +module_param(root, charp, 0600); +MODULE_PARM_DESC(root, "The directory the file system redirects to"); + +/* + * vmb_blocking_wait_on_file() is the only blocking function called from the + * filesystem section, so declare it here. + */ +static int vmb_blocking_wait_on_file(const char *filename); + +/* + * Filesystem section + * + * The following functions implement the filesystem operations necessary to + * present a custom view of the underlying directory. + * + */ + +/* Filesystem utility functions */ + +static inline struct vmb_inode_info *vmb_inode_to_iinfo( + struct inode *inode) +{ + return container_of(inode, struct vmb_inode_info, inode); +} + +static inline struct dentry *vmb_inode_to_actual_dentry( + struct inode *inode) +{ + return vmb_inode_to_iinfo(inode)->actual_dentry; +} + +static inline struct inode *vmb_inode_to_actual_inode( + struct inode *inode) +{ + return vmb_inode_to_actual_dentry(inode)->d_inode; +} + +static int vmb_make_full_name(struct inode *dir, + struct dentry *dentry, char *buf_out, size_t buf_out_size) +{ + struct vmb_inode_info *dir_iinfo; + int ret; + int size_req; + const char *sep; + const char *dirname; + const char *filename; + + BUG_ON(!buf_out); + + /* + * If dir is supplied, contruct the full path of the actual file, + * otherwise it's the root directory. + */ + if (dir) { + BUG_ON(!dentry); + + if (!dentry->d_name.name) { + VMBLOCK_ERROR("dentry name is empty"); + ret = -EINVAL; + goto out_name; + } + + dir_iinfo = vmb_inode_to_iinfo(dir); + + dirname = dir_iinfo->name; + filename = dentry->d_name.name; + } else { + dirname = root; + filename = ""; + } + + if ((strlen(dirname) <= 1) || (strlen(filename) == 0)) + sep = ""; + else + sep = "/"; + + size_req = strlen(dirname) + strlen(sep) + strlen(filename) + 1; + + if (size_req > buf_out_size) { + VMBLOCK_ERROR("path too long"); + ret = -ENAMETOOLONG; + goto out_name; + } + + snprintf(buf_out, buf_out_size, "%s%s%s", dirname, sep, filename); + ret = 0; + +out_name: + return ret; +} + +static struct inode *vmb_get_inode(struct super_block *sb, + struct inode *dir, struct dentry *dentry, ino_t ino) +{ + struct vmb_inode_info *iinfo; + struct nameidata actual_nd; + struct inode *inode; + + BUG_ON(ino < VMBLOCK_ROOT_INO); + + inode = iget_locked(sb, ino); + + if (!inode) + goto out_inode; + + iinfo = vmb_inode_to_iinfo(inode); + + if (inode->i_state & I_NEW) { + iinfo->name[0] = '\0'; + iinfo->name_len = 0; + iinfo->actual_dentry = NULL; + unlock_new_inode(inode); + } + + if (vmb_make_full_name(dir, dentry, iinfo->name, + sizeof iinfo->name) < 0) { + VMBLOCK_ERROR("could not make full name"); + iput(inode); + goto out_inode; + } + + if (path_lookup(iinfo->name, 0, &actual_nd)) { + /* + * This file does not exist, so we create an inode that doesn't + * know about its underlying file. Operations that create files + * and directories need an inode to operate on even if there is + * no actual file yet. + */ + iinfo->actual_dentry = NULL; + } else { + iinfo->actual_dentry = actual_nd.path.dentry; + path_put(&actual_nd.path); + } + +out_inode: + return inode; +} + +/* Link dentry operations */ + +static int vmb_dentry_revalidate(struct dentry *dentry, + struct nameidata *nd) +{ + struct vmb_inode_info *iinfo; + struct nameidata actual_nd; + struct dentry *actual_dentry; + int ret; + + if (!dentry) { + VMBLOCK_WARNING("invalid args from kernel"); + return 0; + } + + /* + * If a dentry does not have an inode associated with it then + * we are dealing with a negative dentry. Always invalidate a negative + * dentry which will cause a fresh lookup. + */ + if (!dentry->d_inode) + return 0; + + + iinfo = vmb_inode_to_iinfo(dentry->d_inode); + if (!iinfo) { + VMBLOCK_WARNING("dentry has no fs-specific data"); + return 0; + } + + /* Block if there is a pending block on this file */ + vmb_blocking_wait_on_file(iinfo->name); + + /* + * If the actual dentry has a revalidate function, we'll let it figure + * out whether the dentry is still valid. If not, do a path lookup to + * ensure that the file still exists. + */ + actual_dentry = iinfo->actual_dentry; + + if (actual_dentry && + actual_dentry->d_op && + actual_dentry->d_op->d_revalidate) { + return actual_dentry->d_op->d_revalidate(actual_dentry, + nd); + } + + if (path_lookup(iinfo->name, 0, &actual_nd)) { + VMBLOCK_INFO("[%s] no longer exists", iinfo->name); + return 0; + } + ret = actual_nd.path.dentry && + actual_nd.path.dentry->d_inode; + path_put(&actual_nd.path); + + VMBLOCK_DEBUG("[%s] %s revalidated", iinfo->name, ret ? "" : "not"); + return ret; +} + +static struct dentry_operations vmb_link_dentry_ops = { + .d_revalidate = vmb_dentry_revalidate, +}; + +/* Link inode operations */ + +static int vmb_readlink(struct dentry *dentry, char __user *buffer, + int buflen) +{ + struct vmb_inode_info *iinfo; + + if (!dentry || !buffer) { + VMBLOCK_WARNING("invalid args from kernel"); + return -EINVAL; + } + + iinfo = vmb_inode_to_iinfo(dentry->d_inode); + if (!iinfo) + return -EINVAL; + + return vfs_readlink(dentry, buffer, buflen, iinfo->name); +} + +static void *vmb_follow_link(struct dentry *dentry, struct nameidata *nd) +{ + int ret; + struct vmb_inode_info *iinfo; + + if (!dentry) { + VMBLOCK_WARNING("invalid args from kernel"); + ret = -EINVAL; + goto out_link; + } + + iinfo = vmb_inode_to_iinfo(dentry->d_inode); + if (!iinfo) { + ret = -EINVAL; + goto out_link; + } + + ret = vfs_follow_link(nd, iinfo->name); + +out_link: + return ERR_PTR(ret); +} + +static const struct inode_operations vmb_link_inode_ops = { + .readlink = vmb_readlink, + .follow_link = vmb_follow_link, +}; + +/* Root inode operations */ + +static ino_t vmb_get_next_ino(void) +{ + static DEFINE_SPINLOCK(vmb_ino_lock); + static ino_t next_ino = VMBLOCK_ROOT_INO + 1; + ino_t ret; + + spin_lock(&vmb_ino_lock); + ret = next_ino++; + spin_unlock(&vmb_ino_lock); + + return ret; +} + +static struct dentry *vmb_lookup(struct inode *dir, struct dentry *dentry, + struct nameidata *nd) +{ + char *filename; + struct inode *inode; + int ret; + + if (!dir || !dentry) { + VMBLOCK_WARNING("invalid args from kernel"); + return ERR_PTR(-EINVAL); + } + + /* + * The kernel should only pass us our own inodes, but check just to be + * safe. + */ + if (!vmb_inode_to_iinfo(dir)) { + VMBLOCK_WARNING("invalid inode provided"); + return ERR_PTR(-EINVAL); + } + + /* Get a slab from the kernel's names_cache of PATH_MAX-sized buffers */ + filename = __getname(); + if (!filename) { + VMBLOCK_WARNING("unable to obtain memory for filename"); + return ERR_PTR(-ENOMEM); + } + + ret = vmb_make_full_name(dir, dentry, filename, PATH_MAX); + if (ret < 0) { + VMBLOCK_WARNING("could not construct full name"); + __putname(filename); + return ERR_PTR(ret); + } + + /* Block if there is a pending block on this file */ + vmb_blocking_wait_on_file(filename); + __putname(filename); + + inode = vmb_get_inode(dir->i_sb, dir, dentry, + vmb_get_next_ino()); + if (!inode) { + VMBLOCK_WARNING("failed to get inode"); + return ERR_PTR(-ENOMEM); + } + + dentry->d_op = &vmb_link_dentry_ops; + dentry->d_time = jiffies; + + /* + * If the actual file's dentry doesn't have an inode, it means the file + * we are redirecting to doesn't exist. Give back the inode that was + * created for this and add a NULL dentry->inode entry in the dcache. + * (The NULL entry is added so ops to create files/directories are + * invoked by VFS.) + */ + if (!vmb_inode_to_actual_dentry(inode) || + !vmb_inode_to_actual_inode(inode)) { + iput(inode); + d_add(dentry, NULL); + return NULL; + } + + inode->i_mode = S_IFLNK | S_IRWXUGO; + inode->i_size = vmb_inode_to_iinfo(inode)->name_len; + inode->i_version = 1; + inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; + inode->i_uid = inode->i_gid = 0; + inode->i_op = &vmb_link_inode_ops; + + d_add(dentry, inode); + return NULL; +} + +static const struct inode_operations vmb_root_inode_ops = { + .lookup = vmb_lookup, +}; + +/* Root file operations */ + +static int vmb_filldir(void *buf, + const char *name, + int namelen, + loff_t offset, + u64 ino, + unsigned int d_type) +{ + struct vmb_filldir_info *info = (struct vmb_filldir_info *)buf; + + /* Specify DT_LNK regardless */ + return info->filldir(info->dirent, name, namelen, offset, ino, DT_LNK); +} + +static int vmb_readdir(struct file *file, void *dirent, filldir_t filldir) +{ + int ret; + struct vmb_filldir_info info; + struct file *actual_file; + + if (!file) { + VMBLOCK_WARNING("invalid args from kernel"); + return -EINVAL; + } + + actual_file = file->private_data; + if (!actual_file) { + VMBLOCK_WARNING("no actual file found"); + return -EINVAL; + } + + info.filldir = filldir; + info.dirent = dirent; + + actual_file->f_pos = file->f_pos; + ret = vfs_readdir(actual_file, vmb_filldir, &info); + file->f_pos = actual_file->f_pos; + + return ret; +} + +static int vmb_open(struct inode *inode, struct file *file) +{ + struct vmb_inode_info *iinfo; + struct file *actual_file; + + if (!inode || !file || !vmb_inode_to_iinfo(inode)) { + VMBLOCK_WARNING("invalid args from kernel"); + return -EINVAL; + } + + iinfo = vmb_inode_to_iinfo(inode); + + /* + * Get an open file for the directory we are redirecting to. This + * ensures we can gracefully handle cases where that directory is + * removed after we are mounted. + */ + actual_file = filp_open(iinfo->name, file->f_flags, file->f_flags); + if (IS_ERR(actual_file)) { + VMBLOCK_WARNING("could not open file [%s]", iinfo->name); + file->private_data = NULL; + return PTR_ERR(actual_file); + } + + /* + * If the file opened is the same as the one retrieved for the file + * then we shouldn't allow the open to happen. This can only occur if + * the redirected root directory specified at mount time is the same as + * where the mount is placed. Later in vmb_readdir() we'd call + * vfs_readdir() and that would try to acquire the inode's semaphore; + * if the two inodes are the same we'll deadlock. + */ + if (actual_file->f_dentry && inode == actual_file->f_dentry->d_inode) { + VMBLOCK_WARNING("identical inode encountered, " + "open cannot succeed"); + if (filp_close(actual_file, current->files) < 0) + VMBLOCK_WARNING("unable to close opened file"); + return -EINVAL; + } + + file->private_data = actual_file; + return 0; +} + +static int vmb_release(struct inode *inode, struct file *file) +{ + int ret; + struct file *actual_file; + + if (!inode || !file) { + VMBLOCK_WARNING("invalid args from kernel"); + return -EINVAL; + } + + actual_file = file->private_data; + if (!actual_file) { + VMBLOCK_WARNING("no actual file found"); + return -EINVAL; + } + + ret = filp_close(actual_file, current->files); + + return ret; +} + +static const struct file_operations vmb_root_file_ops = { + .readdir = vmb_readdir, + .open = vmb_open, + .release = vmb_release, +}; + +/* Super block operations */ + +static struct inode *vmb_alloc_inode(struct super_block *sb) +{ + struct vmb_inode_info *iinfo; + + iinfo = kmem_cache_alloc(vmb_inode_info_cache, GFP_KERNEL); + if (!iinfo) { + VMBLOCK_ERROR("could not allocate inode info"); + return NULL; + } + + return &iinfo->inode; +} + +static void vmb_destroy_inode(struct inode *inode) +{ + kmem_cache_free(vmb_inode_info_cache, vmb_inode_to_iinfo(inode)); +} + +static int vmb_statfs(struct dentry *dentry, struct kstatfs *stat) +{ + if (!stat) + return -EINVAL; + + stat->f_type = VMBLOCK_SUPER_MAGIC; + stat->f_bsize = 0; + stat->f_namelen = NAME_MAX; + stat->f_blocks = 0; + stat->f_bfree = 0; + stat->f_bavail = 0; + + return 0; +} + +static const struct super_operations vmb_super_ops = { + .alloc_inode = vmb_alloc_inode, + .destroy_inode = vmb_destroy_inode, + .statfs = vmb_statfs, +}; + +/* File system operations */ + +static int vmb_fill_super(struct super_block *sb, void *data, int silent) +{ + struct inode *root_inode; + struct dentry *root_dentry; + + sb->s_magic = VMBLOCK_SUPER_MAGIC; + sb->s_blocksize = VMBLOCK_BLOCKSIZE; + sb->s_op = &vmb_super_ops; + + root_inode = vmb_get_inode(sb, NULL, NULL, VMBLOCK_ROOT_INO); + + if (!root_inode) + return -EINVAL; + + if (!vmb_inode_to_iinfo(root_inode) || + !vmb_inode_to_actual_dentry(root_inode) || + !vmb_inode_to_actual_inode(root_inode) || + !S_ISDIR(vmb_inode_to_actual_inode(root_inode)->i_mode)) { + iput(root_inode); + return -EINVAL; + } + + root_dentry = d_alloc_root(root_inode); + if (!root_dentry) { + iput(root_inode); + return -ENOMEM; + } + sb->s_root = root_dentry; + + root_inode->i_op = &vmb_root_inode_ops; + root_inode->i_fop = &vmb_root_file_ops; + root_inode->i_mode = S_IFDIR | S_IRUGO | S_IXUGO; + + VMBLOCK_INFO("%s: file system mounted", VMBLOCK_FS_NAME); + + return 0; +} + +static int vmb_get_sb(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data, struct vfsmount *mnt) +{ + return get_sb_nodev(fs_type, flags, data, vmb_fill_super, mnt); + +} + +static struct file_system_type vmb_fs_type = { + .owner = THIS_MODULE, + .name = VMBLOCK_FS_NAME, + .get_sb = vmb_get_sb, + .kill_sb = kill_anon_super, +}; + +static void vmb_inode_info_cache_ctor(void *slab_elem) +{ + struct vmb_inode_info *iinfo = slab_elem; + + inode_init_once(&iinfo->inode); +} + +static int __init vmb_fs_init(const char *fsroot) +{ + int err; + + if (!fsroot) { + VMBLOCK_ERROR("no root specified (missing module param?)"); + err = -EINVAL; + goto out_fs; + } + + vmb_inode_info_cache = kmem_cache_create( + "vmb_inode_info_cache", + sizeof(struct vmb_inode_info), 0, + SLAB_HWCACHE_ALIGN, + vmb_inode_info_cache_ctor); + + if (!vmb_inode_info_cache) { + VMBLOCK_ERROR("could not initialize inode cache"); + err = -ENOMEM; + goto out_fs; + } + + err = register_filesystem(&vmb_fs_type); + if (err) { + VMBLOCK_ERROR("could not register filesystem"); + kmem_cache_destroy(vmb_inode_info_cache); + goto out_fs; + } + +out_fs: + return err; +} + +static void __exit vmb_fs_cleanup(void) +{ + unregister_filesystem(&vmb_fs_type); + + kmem_cache_destroy(vmb_inode_info_cache); +} + +/* + * Blocking section + * + * The following functions implement the blocking functionality. + */ + +static LIST_HEAD(vmb_blocked_files); +static rwlock_t vmb_blocked_files_lock; +static struct kmem_cache *vmb_blocking_info_cache; + +static int vmb_blocking_init(void) +{ + BUG_ON(vmb_blocking_info_cache); + + vmb_blocking_info_cache = kmem_cache_create("vmb_blocking_info_cache", + sizeof(struct vmb_blocking_info), 0, + SLAB_HWCACHE_ALIGN, NULL); + if (!vmb_blocking_info_cache) + return -ENOMEM; + + INIT_LIST_HEAD(&vmb_blocked_files); + rwlock_init(&vmb_blocked_files_lock); + + return 0; +} + +static void vmb_blocking_cleanup(void) +{ + BUG_ON(!vmb_blocking_info_cache); + BUG_ON(!list_empty(&vmb_blocked_files)); + + kmem_cache_destroy(vmb_blocking_info_cache); +} + +static struct vmb_blocking_info *vmb_blocking_alloc_block( + struct kmem_cache *cache, + const char *filename, + const struct file *blocker) +{ + struct vmb_blocking_info *block; + size_t ret; + + /* Initialize this file's block structure. */ + block = kmem_cache_alloc(vmb_blocking_info_cache, GFP_KERNEL); + if (!block) + goto out_alloc_block; + + ret = strlcpy(block->filename, filename, sizeof block->filename); + if (ret >= sizeof block->filename) { + VMBLOCK_WARNING("filename is too large"); + kmem_cache_free(vmb_blocking_info_cache, block); + goto out_alloc_block; + } + + INIT_LIST_HEAD(&block->links); + atomic_set(&block->refcount, 1); + init_completion(&block->completion); + block->blocker = (struct file *)blocker; + +out_alloc_block: + return block; +} + +static void vmb_blocking_free_block(struct kmem_cache *cache, + struct vmb_blocking_info *block) +{ + BUG_ON(!cache); + BUG_ON(!block); + + kmem_cache_free(cache, block); +} + +static struct vmb_blocking_info *vmb_blocking_get_block( + const char *filename, + const struct file *blocker) +{ + struct vmb_blocking_info *curr_block; + + list_for_each_entry(curr_block, &vmb_blocked_files, links) { + if ((blocker == VMBLOCK_UNKNOWN_BLOCKER || + blocker == curr_block->blocker) && + strcmp(curr_block->filename, filename) == 0) { + atomic_inc(&curr_block->refcount); + return curr_block; + } + } + + return NULL; +} + +static bool vmb_block_exists(const char *filename) +{ + struct vmb_blocking_info *block = vmb_blocking_get_block(filename, + VMBLOCK_UNKNOWN_BLOCKER); + + if (block) { + /* get_block above will have incremented refcount */ + atomic_dec(&block->refcount); + return true; + } + + return false; +} + +static int vmb_blocking_add_file_block(const char *filename, + const struct file *blocker) +{ + int ret = 0; + struct vmb_blocking_info *block; + + BUG_ON(!filename); + + /* Create a new block. */ + block = vmb_blocking_alloc_block(vmb_blocking_info_cache, filename, + blocker); + if (!block) { + VMBLOCK_WARNING("out of memory"); + ret = -ENOMEM; + goto out_add_file_block; + } + write_lock(&vmb_blocked_files_lock); + + /* + * Prevent duplicate blocks of any filename. Done under same lock + * as list addition to ensure check for and adding of file are atomic. + */ + if (vmb_block_exists(filename)) { + VMBLOCK_WARNING("block already exists for [%s]", filename); + write_unlock(&vmb_blocked_files_lock); + vmb_blocking_free_block(vmb_blocking_info_cache, block); + ret = -EEXIST; + goto out_add_file_block; + } + + list_add_tail(&block->links, &vmb_blocked_files); + + write_unlock(&vmb_blocked_files_lock); + + VMBLOCK_INFO("added block for [%s]", filename); + +out_add_file_block: + return ret; +} + +static void vmb_blocking_free_complete(const struct file *blocker, + struct vmb_blocking_info *block) +{ + /* + * struct vmb_blocking_info's, as the result of placing + * a block on a file or directory, reference + * themselves. When the block is lifted, we need to + * remove this self-reference and handle the result + * appropriately. + */ + if (atomic_dec_and_test(&block->refcount)) { + /* Free blocks without any waiters ... */ + VMBLOCK_INFO("Freeing block with no waiters for blocker " + "[%p] (%s)", blocker, block->filename); + vmb_blocking_free_block(vmb_blocking_info_cache, block); + } else { + /* ... or wakeup the waiting threads */ + VMBLOCK_INFO("Completing block for blocker " + "[%p] (%s)", blocker, block->filename); + complete_all(&block->completion); + } +} + +static int vmb_blocking_remove_file_block(const char *filename, + const struct file *blocker) +{ + int ret = 0; + struct vmb_blocking_info *block; + + BUG_ON(!filename); + + write_lock(&vmb_blocked_files_lock); + + block = vmb_blocking_get_block(filename, blocker); + if (!block) { + write_unlock(&vmb_blocked_files_lock); + ret = -ENOENT; + goto out_remove_file_block; + } + + list_del(&block->links); + write_unlock(&vmb_blocked_files_lock); + + /* Undo vmb_blocking_get_block's refcount increment first */ + atomic_dec(&block->refcount); + + /* + * Now remove /our/ reference (as opposed to references by waiting + * threads) + */ + vmb_blocking_free_complete(blocker, block); + +out_remove_file_block: + return ret; +} + +static unsigned int vmb_blocking_remove_all_blocks(const struct file *blocker) +{ + struct vmb_blocking_info *curr_block, *tmp; + /* struct list_head *tmp; */ + unsigned int removed = 0; + + write_lock(&vmb_blocked_files_lock); + + list_for_each_entry_safe(curr_block, tmp, &vmb_blocked_files, links) { + if (blocker == curr_block->blocker || + blocker == VMBLOCK_UNKNOWN_BLOCKER) { + + list_del(&curr_block->links); + + /* + * We count only entries removed from the -list-, + * regardless of whether or not other waiters exist. + */ + ++removed; + + vmb_blocking_free_complete(blocker, curr_block); + } + } + + write_unlock(&vmb_blocked_files_lock); + + return removed; +} + +static int vmb_blocking_wait_on_file(const char *filename) +{ + struct vmb_blocking_info *block; + int error = 0; + + BUG_ON(!filename); + + read_lock(&vmb_blocked_files_lock); + block = vmb_blocking_get_block(filename, VMBLOCK_UNKNOWN_BLOCKER); + read_unlock(&vmb_blocked_files_lock); + + if (!block) + /* This file is not blocked, just return */ + goto out_wait_on_file; + + VMBLOCK_INFO("(%d) Waiting for completion on [%s]", + current->pid, filename); + + wait_for_completion(&block->completion); + + VMBLOCK_INFO("(%d) Wokeup from block on [%s]", + current->pid, filename); + + /* + * The assumptions here are as follows: + * 1. The struct vmb_blocking_info holds a reference to itself. + * (struct vmb_blocking_info's refcount is initialized to 1) + * 2. struct vmb_blocking_info's self reference is deleted only when + * it is /also/ removed removed from the block list. + * + * Therefore, if the reference count hits zero, it's because the block + * is no longer in the list, and there is no chance of another thread + * finding and referencing this block between our dec_and_test and + * freeing it. + */ + if (atomic_dec_and_test(&block->refcount)) { + /* We were the last thread, so clean up */ + VMBLOCK_INFO("(%d) I am the last to wakeup, freeing the " + "block on [%s]", current->pid, filename); + vmb_blocking_free_block(vmb_blocking_info_cache, block); + } + +out_wait_on_file: + return error; +} + +#ifdef CONFIG_VMBLOCK_DEBUG +static void vmb_blocking_list_file_blocks(void) +{ + struct vmb_blocking_info *curr_block; + int count = 0; + + read_lock(&vmb_blocked_files_lock); + + list_for_each_entry(curr_block, &vmb_blocked_files, links) { + VMBLOCK_DEBUG("(%d) Filename: [%s], Blocker: [%p]", + count++, curr_block->filename, curr_block->blocker); + } + + read_unlock(&vmb_blocked_files_lock); + + if (!count) + VMBLOCK_DEBUG("No blocks currently exist"); +} +#endif + +/* + * Control section + * + * The following functions implement the control functionality. + */ + +static ssize_t vmb_control_write(struct file *file, const char __user *buf, + size_t cmd, loff_t *ppos) +{ + long ret; + ssize_t i; + char *filename; + +#ifdef CONFIG_VMBLOCK_DEBUG + if (cmd == VMBLOCK_LIST_FILEBLOCKS) { + vmb_blocking_list_file_blocks(); + return 0; + } +#endif + + filename = getname(buf); + ret = PTR_ERR(filename); + if (IS_ERR(filename)) { + VMBLOCK_WARNING("Could not get filename from user buffer"); + goto exit; + } + + /* Remove all trailing path separators. */ + for (i = strlen(filename) - 1; i >= 0 && filename[i] == '/'; i--) + filename[i] = '\0'; + + if (i < 0) { + ret = -EINVAL; + goto exit_putname; + } + + switch (cmd) { + case VMBLOCK_ADD_FILEBLOCK: + ret = vmb_blocking_add_file_block(filename, file); + break; + case VMBLOCK_DEL_FILEBLOCK: + ret = vmb_blocking_remove_file_block(filename, file); + break; + default: + VMBLOCK_WARNING("unrecognized command (%u) received", + (unsigned)cmd); + ret = -EINVAL; + break; + } + + +exit_putname: + putname(filename); + +exit: + return ret; +} + +static int vmb_control_release(struct inode *inode, struct file *file) +{ + vmb_blocking_remove_all_blocks(file); + + return 0; +} + +static const struct file_operations vmb_control_file_ops = { + .owner = THIS_MODULE, + .write = vmb_control_write, + .release = vmb_control_release, +}; + +static struct proc_dir_entry *vmb_control_proc_dir_entry; + +static int vmb_control_proc_init(void) +{ + int ret = 0; + + struct proc_dir_entry *control_proc_entry; + struct proc_dir_entry *control_proc_mountpoint; + + /* Create /proc/fs/vmblock */ + vmb_control_proc_dir_entry = proc_mkdir( + VMBLOCK_CONTROL_PROC_DIRNAME, NULL); + + if (!vmb_control_proc_dir_entry) { + VMBLOCK_WARNING("could not create /proc/" + VMBLOCK_CONTROL_PROC_DIRNAME); + ret = -EINVAL; + goto out_control_proc_init; + } + + vmb_control_proc_dir_entry->owner = THIS_MODULE; + + /* Create /proc/fs/vmblock/mountPoint */ + control_proc_mountpoint = proc_mkdir(VMBLOCK_CONTROL_MOUNTPOINT, + vmb_control_proc_dir_entry); + + if (!control_proc_mountpoint) { + VMBLOCK_WARNING("could not create " VMBLOCK_MOUNT_POINT); + remove_proc_entry(VMBLOCK_CONTROL_PROC_DIRNAME, NULL); + ret = -EINVAL; + goto out_control_proc_init; + } + + control_proc_mountpoint->owner = THIS_MODULE; + + /* Create /proc/fs/vmblock/dev */ + control_proc_entry = create_proc_entry(VMBLOCK_CONTROL_DEVNAME, + VMBLOCK_CONTROL_MODE, vmb_control_proc_dir_entry); + + if (!control_proc_entry) { + VMBLOCK_WARNING("could not create " VMBLOCK_DEVICE); + remove_proc_entry(VMBLOCK_CONTROL_MOUNTPOINT, + vmb_control_proc_dir_entry); + remove_proc_entry(VMBLOCK_CONTROL_PROC_DIRNAME, NULL); + ret = -EINVAL; + goto out_control_proc_init; + } + + control_proc_entry->proc_fops = &vmb_control_file_ops; + +out_control_proc_init: + return ret; +} + +static void vmb_control_proc_cleanup(void) +{ + if (vmb_control_proc_dir_entry) { + remove_proc_entry(VMBLOCK_CONTROL_MOUNTPOINT, + vmb_control_proc_dir_entry); + remove_proc_entry(VMBLOCK_CONTROL_DEVNAME, + vmb_control_proc_dir_entry); + remove_proc_entry(VMBLOCK_CONTROL_PROC_DIRNAME, NULL); + } +} + +static int __init vmb_control_init(void) +{ + int ret; + + ret = vmb_blocking_init(); + if (ret < 0) { + VMBLOCK_WARNING("could not initialize blocking ops"); + goto out_control_init; + } + + ret = vmb_control_proc_init(); + if (ret < 0) { + VMBLOCK_WARNING("could not setup proc device"); + vmb_blocking_cleanup(); + } + +out_control_init: + return ret; +} + +static void vmb_control_cleanup(void) +{ + vmb_control_proc_cleanup(); + vmb_blocking_cleanup(); +} + +/* + * Module section + * + * The following functions implement the usual kernel module entry and exit + * points. + */ + +static int __init vmb_init(void) +{ + int err = vmb_control_init(); + if (err) + goto out; + + err = vmb_fs_init(root); + + if (err) + vmb_control_cleanup(); + +out: + return err; +} + +static void __exit vmb_exit(void) +{ + vmb_fs_cleanup(); + vmb_control_cleanup(); +} + +module_init(vmb_init); +module_exit(vmb_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("VMware, Inc."); +MODULE_DESCRIPTION("VMware Blocking File System"); diff --git a/fs/Makefile b/fs/Makefile index a1482a5..b42fe8c 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -122,3 +122,4 @@ obj-$(CONFIG_HPPFS) += hppfs/ obj-$(CONFIG_DEBUG_FS) += debugfs/ obj-$(CONFIG_OCFS2_FS) += ocfs2/ obj-$(CONFIG_GFS2_FS) += gfs2/ +obj-$(CONFIG_VMBLOCK_FS) += vmblock/ diff --git a/include/linux/Kbuild b/include/linux/Kbuild index b68ec09..e6b6bd0 100644 --- a/include/linux/Kbuild +++ b/include/linux/Kbuild @@ -161,6 +161,7 @@ header-y += veth.h header-y += video_decoder.h header-y += video_encoder.h header-y += videotext.h +header-y += vmblock.h header-y += x25.h unifdef-y += acct.h diff --git a/include/linux/vmblock.h b/include/linux/vmblock.h new file mode 100644 index 0000000..e70ad81 --- /dev/null +++ b/include/linux/vmblock.h @@ -0,0 +1,49 @@ +/********************************************************* + * Copyright (C) 2006 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 and no later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + *********************************************************/ + +/* + * vmblock.h -- + * + * User-level interface to the vmblock device. + */ + +#ifndef _LINUX_VMBLOCK_H +#define _LINUX_VMBLOCK_H + +#define VMBLOCK_FS_NAME "vmblock" + +/* Commands for the control half of vmblock driver */ +#define VMBLOCK_ADD_FILEBLOCK 98 +#define VMBLOCK_DEL_FILEBLOCK 99 +#ifdef CONFIG_VMBLOCK_DEBUG +# define VMBLOCK_LIST_FILEBLOCKS 100 +#endif +#define VMBLOCK_CONTROL_DIRNAME VMBLOCK_FS_NAME +#define VMBLOCK_CONTROL_DEVNAME "dev" +#define VMBLOCK_CONTROL_MOUNTPOINT "mountPoint" +#define VMBLOCK_CONTROL_PROC_DIRNAME "fs/" VMBLOCK_CONTROL_DIRNAME + +#define VMBLOCK_MOUNT_POINT "/proc/" VMBLOCK_CONTROL_PROC_DIRNAME \ + "/" VMBLOCK_CONTROL_MOUNTPOINT +#define VMBLOCK_DEVICE "/proc/" VMBLOCK_CONTROL_PROC_DIRNAME \ + "/" VMBLOCK_CONTROL_DEVNAME +#define VMBLOCK_DEVICE_MODE O_WRONLY +#define VMBLOCK_CONTROL(fd, op, path) write(fd, path, op) +#define VMBLOCK_CONTROL_MODE (S_IRUSR | S_IFREG) + +#endif /* _LINUX_VMBLOCK_H */ -- 1.5.5.1.308.g1fbb5 ------------------------------------------------------------------------- This SF.Net email is sponsored by the Moblin Your Move Developer's challenge Build the coolest Linux based applications with Moblin SDK & win great prizes Grand prize is a trip for two to an Open Source event anywhere in the world http://moblin-contest.org/redirect.php?banner_id=100&url=/ _______________________________________________ open-vm-tools-devel mailing list open-vm-tools-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/open-vm-tools-devel