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
[email protected]
https://lists.sourceforge.net/lists/listinfo/open-vm-tools-devel