first commit

This commit is contained in:
Fabio Belavenuto
2022-07-01 13:57:55 -03:00
commit a08662802d
1085 changed files with 341425 additions and 0 deletions

6
addons/9p/install.sh Normal file
View File

@@ -0,0 +1,6 @@
if [ "${1}" = "rd" ]; then
echo "Installing module for Plan 9 Resource Sharing Support (9P2000)"
${INSMOD} "/modules/9pnet.ko"
${INSMOD} "/modules/9pnet_virtio.ko"
${INSMOD} "/modules/9p.ko" ${PARAMS}
fi

28
addons/9p/manifest.yml Normal file
View File

@@ -0,0 +1,28 @@
version: 1
name: 9p
description: "Driver for Plan 9 Resource Sharing Support (9P2000)"
available-for:
bromolow-3.10.108:
install-script: &script "install.sh"
modules: true
apollolake-4.4.180:
install-script: *script
modules: true
broadwell-4.4.180:
install-script: *script
modules: true
broadwellnk-4.4.180:
install-script: *script
modules: true
denverton-4.4.180:
install-script: *script
modules: true
geminilake-4.4.180:
install-script: *script
modules: true
v1000-4.4.180:
install-script: *script
modules: true
purley-4.4.180:
install-script: *script
modules: true

View File

@@ -0,0 +1,36 @@
obj-m := 9p.o
9p-objs := \
vfs_super.o \
vfs_inode.o \
vfs_inode_dotl.o \
vfs_addr.o \
vfs_file.o \
vfs_dir.o \
vfs_dentry.o \
v9fs.o \
fid.o \
xattr.o \
xattr_user.o
9p-y += cache.o
9p-n += acl.o
obj-m := 9pnet.o
obj-m += 9pnet_virtio.o
obj-n += 9pnet_rdma.o
9pnet-objs := \
mod.o \
client.o \
error.o \
util.o \
protocol.o \
trans_fd.o \
trans_common.o \
9pnet_virtio-objs := \
trans_virtio.o
9pnet_rdma-objs := \
trans_rdma.o

View File

@@ -0,0 +1,375 @@
/*
* Copyright IBM Corporation, 2010
* Author Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2.1 of the GNU Lesser General Public License
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it would be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/posix_acl_xattr.h>
#include "xattr.h"
#include "acl.h"
#include "v9fs.h"
#include "v9fs_vfs.h"
#include "fid.h"
static struct posix_acl *__v9fs_get_acl(struct p9_fid *fid, char *name)
{
ssize_t size;
void *value = NULL;
struct posix_acl *acl = NULL;
size = v9fs_fid_xattr_get(fid, name, NULL, 0);
if (size > 0) {
value = kzalloc(size, GFP_NOFS);
if (!value)
return ERR_PTR(-ENOMEM);
size = v9fs_fid_xattr_get(fid, name, value, size);
if (size > 0) {
acl = posix_acl_from_xattr(&init_user_ns, value, size);
if (IS_ERR(acl))
goto err_out;
}
} else if (size == -ENODATA || size == 0 ||
size == -ENOSYS || size == -EOPNOTSUPP) {
acl = NULL;
} else
acl = ERR_PTR(-EIO);
err_out:
kfree(value);
return acl;
}
int v9fs_get_acl(struct inode *inode, struct p9_fid *fid)
{
int retval = 0;
struct posix_acl *pacl, *dacl;
struct v9fs_session_info *v9ses;
v9ses = v9fs_inode2v9ses(inode);
if (((v9ses->flags & V9FS_ACCESS_MASK) != V9FS_ACCESS_CLIENT) ||
((v9ses->flags & V9FS_ACL_MASK) != V9FS_POSIX_ACL)) {
set_cached_acl(inode, ACL_TYPE_DEFAULT, NULL);
set_cached_acl(inode, ACL_TYPE_ACCESS, NULL);
return 0;
}
/* get the default/access acl values and cache them */
dacl = __v9fs_get_acl(fid, POSIX_ACL_XATTR_DEFAULT);
pacl = __v9fs_get_acl(fid, POSIX_ACL_XATTR_ACCESS);
if (!IS_ERR(dacl) && !IS_ERR(pacl)) {
set_cached_acl(inode, ACL_TYPE_DEFAULT, dacl);
set_cached_acl(inode, ACL_TYPE_ACCESS, pacl);
} else
retval = -EIO;
if (!IS_ERR(dacl))
posix_acl_release(dacl);
if (!IS_ERR(pacl))
posix_acl_release(pacl);
return retval;
}
static struct posix_acl *v9fs_get_cached_acl(struct inode *inode, int type)
{
struct posix_acl *acl;
/*
* 9p Always cache the acl value when
* instantiating the inode (v9fs_inode_from_fid)
*/
acl = get_cached_acl(inode, type);
BUG_ON(acl == ACL_NOT_CACHED);
return acl;
}
struct posix_acl *v9fs_iop_get_acl(struct inode *inode, int type)
{
struct v9fs_session_info *v9ses;
v9ses = v9fs_inode2v9ses(inode);
if (((v9ses->flags & V9FS_ACCESS_MASK) != V9FS_ACCESS_CLIENT) ||
((v9ses->flags & V9FS_ACL_MASK) != V9FS_POSIX_ACL)) {
/*
* On access = client and acl = on mode get the acl
* values from the server
*/
return NULL;
}
return v9fs_get_cached_acl(inode, type);
}
static int v9fs_set_acl(struct p9_fid *fid, int type, struct posix_acl *acl)
{
int retval;
char *name;
size_t size;
void *buffer;
if (!acl)
return 0;
/* Set a setxattr request to server */
size = posix_acl_xattr_size(acl->a_count);
buffer = kmalloc(size, GFP_KERNEL);
if (!buffer)
return -ENOMEM;
retval = posix_acl_to_xattr(&init_user_ns, acl, buffer, size);
if (retval < 0)
goto err_free_out;
switch (type) {
case ACL_TYPE_ACCESS:
name = POSIX_ACL_XATTR_ACCESS;
break;
case ACL_TYPE_DEFAULT:
name = POSIX_ACL_XATTR_DEFAULT;
break;
default:
BUG();
}
retval = v9fs_fid_xattr_set(fid, name, buffer, size, 0);
err_free_out:
kfree(buffer);
return retval;
}
int v9fs_acl_chmod(struct inode *inode, struct p9_fid *fid)
{
int retval = 0;
struct posix_acl *acl;
if (S_ISLNK(inode->i_mode))
return -EOPNOTSUPP;
acl = v9fs_get_cached_acl(inode, ACL_TYPE_ACCESS);
if (acl) {
retval = posix_acl_chmod(&acl, GFP_KERNEL, inode->i_mode);
if (retval)
return retval;
set_cached_acl(inode, ACL_TYPE_ACCESS, acl);
retval = v9fs_set_acl(fid, ACL_TYPE_ACCESS, acl);
posix_acl_release(acl);
}
return retval;
}
int v9fs_set_create_acl(struct inode *inode, struct p9_fid *fid,
struct posix_acl *dacl, struct posix_acl *acl)
{
set_cached_acl(inode, ACL_TYPE_DEFAULT, dacl);
set_cached_acl(inode, ACL_TYPE_ACCESS, acl);
v9fs_set_acl(fid, ACL_TYPE_DEFAULT, dacl);
v9fs_set_acl(fid, ACL_TYPE_ACCESS, acl);
return 0;
}
void v9fs_put_acl(struct posix_acl *dacl,
struct posix_acl *acl)
{
posix_acl_release(dacl);
posix_acl_release(acl);
}
int v9fs_acl_mode(struct inode *dir, umode_t *modep,
struct posix_acl **dpacl, struct posix_acl **pacl)
{
int retval = 0;
umode_t mode = *modep;
struct posix_acl *acl = NULL;
if (!S_ISLNK(mode)) {
acl = v9fs_get_cached_acl(dir, ACL_TYPE_DEFAULT);
if (IS_ERR(acl))
return PTR_ERR(acl);
if (!acl)
mode &= ~current_umask();
}
if (acl) {
if (S_ISDIR(mode))
*dpacl = posix_acl_dup(acl);
retval = posix_acl_create(&acl, GFP_NOFS, &mode);
if (retval < 0)
return retval;
if (retval > 0)
*pacl = acl;
else
posix_acl_release(acl);
}
*modep = mode;
return 0;
}
static int v9fs_remote_get_acl(struct dentry *dentry, const char *name,
void *buffer, size_t size, int type)
{
char *full_name;
switch (type) {
case ACL_TYPE_ACCESS:
full_name = POSIX_ACL_XATTR_ACCESS;
break;
case ACL_TYPE_DEFAULT:
full_name = POSIX_ACL_XATTR_DEFAULT;
break;
default:
BUG();
}
return v9fs_xattr_get(dentry, full_name, buffer, size);
}
static int v9fs_xattr_get_acl(struct dentry *dentry, const char *name,
void *buffer, size_t size, int type)
{
struct v9fs_session_info *v9ses;
struct posix_acl *acl;
int error;
if (strcmp(name, "") != 0)
return -EINVAL;
v9ses = v9fs_dentry2v9ses(dentry);
/*
* We allow set/get/list of acl when access=client is not specified
*/
if ((v9ses->flags & V9FS_ACCESS_MASK) != V9FS_ACCESS_CLIENT)
return v9fs_remote_get_acl(dentry, name, buffer, size, type);
acl = v9fs_get_cached_acl(dentry->d_inode, type);
if (IS_ERR(acl))
return PTR_ERR(acl);
if (acl == NULL)
return -ENODATA;
error = posix_acl_to_xattr(&init_user_ns, acl, buffer, size);
posix_acl_release(acl);
return error;
}
static int v9fs_remote_set_acl(struct dentry *dentry, const char *name,
const void *value, size_t size,
int flags, int type)
{
char *full_name;
switch (type) {
case ACL_TYPE_ACCESS:
full_name = POSIX_ACL_XATTR_ACCESS;
break;
case ACL_TYPE_DEFAULT:
full_name = POSIX_ACL_XATTR_DEFAULT;
break;
default:
BUG();
}
return v9fs_xattr_set(dentry, full_name, value, size, flags);
}
static int v9fs_xattr_set_acl(struct dentry *dentry, const char *name,
const void *value, size_t size,
int flags, int type)
{
int retval;
struct posix_acl *acl;
struct v9fs_session_info *v9ses;
struct inode *inode = dentry->d_inode;
if (strcmp(name, "") != 0)
return -EINVAL;
v9ses = v9fs_dentry2v9ses(dentry);
/*
* set the attribute on the remote. Without even looking at the
* xattr value. We leave it to the server to validate
*/
if ((v9ses->flags & V9FS_ACCESS_MASK) != V9FS_ACCESS_CLIENT)
return v9fs_remote_set_acl(dentry, name,
value, size, flags, type);
if (S_ISLNK(inode->i_mode))
return -EOPNOTSUPP;
if (!inode_owner_or_capable(inode))
return -EPERM;
if (value) {
/* update the cached acl value */
acl = posix_acl_from_xattr(&init_user_ns, value, size);
if (IS_ERR(acl))
return PTR_ERR(acl);
else if (acl) {
retval = posix_acl_valid(acl);
if (retval)
goto err_out;
}
} else
acl = NULL;
switch (type) {
case ACL_TYPE_ACCESS:
name = POSIX_ACL_XATTR_ACCESS;
if (acl) {
struct iattr iattr;
retval = posix_acl_update_mode(inode, &iattr.ia_mode, &acl);
if (retval)
goto err_out;
if (!acl) {
/*
* ACL can be represented
* by the mode bits. So don't
* update ACL.
*/
value = NULL;
size = 0;
}
iattr.ia_valid = ATTR_MODE;
/* FIXME should we update ctime ?
* What is the following setxattr update the
* mode ?
*/
v9fs_vfs_setattr_dotl(dentry, &iattr);
}
break;
case ACL_TYPE_DEFAULT:
name = POSIX_ACL_XATTR_DEFAULT;
if (!S_ISDIR(inode->i_mode)) {
retval = acl ? -EINVAL : 0;
goto err_out;
}
break;
default:
BUG();
}
retval = v9fs_xattr_set(dentry, name, value, size, flags);
if (!retval)
set_cached_acl(inode, type, acl);
err_out:
posix_acl_release(acl);
return retval;
}
const struct xattr_handler v9fs_xattr_acl_access_handler = {
.prefix = POSIX_ACL_XATTR_ACCESS,
.flags = ACL_TYPE_ACCESS,
.get = v9fs_xattr_get_acl,
.set = v9fs_xattr_set_acl,
};
const struct xattr_handler v9fs_xattr_acl_default_handler = {
.prefix = POSIX_ACL_XATTR_DEFAULT,
.flags = ACL_TYPE_DEFAULT,
.get = v9fs_xattr_get_acl,
.set = v9fs_xattr_set_acl,
};

View File

@@ -0,0 +1,55 @@
/*
* Copyright IBM Corporation, 2010
* Author Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2.1 of the GNU Lesser General Public License
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it would be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
*/
#ifndef FS_9P_ACL_H
#define FS_9P_ACL_H
#ifdef CONFIG_9P_FS_POSIX_ACL
extern int v9fs_get_acl(struct inode *, struct p9_fid *);
extern struct posix_acl *v9fs_iop_get_acl(struct inode *inode, int type);
extern int v9fs_acl_chmod(struct inode *, struct p9_fid *);
extern int v9fs_set_create_acl(struct inode *, struct p9_fid *,
struct posix_acl *, struct posix_acl *);
extern int v9fs_acl_mode(struct inode *dir, umode_t *modep,
struct posix_acl **dpacl, struct posix_acl **pacl);
extern void v9fs_put_acl(struct posix_acl *dacl, struct posix_acl *acl);
#else
#define v9fs_iop_get_acl NULL
static inline int v9fs_get_acl(struct inode *inode, struct p9_fid *fid)
{
return 0;
}
static inline int v9fs_acl_chmod(struct inode *inode, struct p9_fid *fid)
{
return 0;
}
static inline int v9fs_set_create_acl(struct inode *inode,
struct p9_fid *fid,
struct posix_acl *dacl,
struct posix_acl *acl)
{
return 0;
}
static inline void v9fs_put_acl(struct posix_acl *dacl,
struct posix_acl *acl)
{
}
static inline int v9fs_acl_mode(struct inode *dir, umode_t *modep,
struct posix_acl **dpacl,
struct posix_acl **pacl)
{
return 0;
}
#endif
#endif /* FS_9P_XATTR_H */

View File

@@ -0,0 +1,415 @@
/*
* V9FS cache definitions.
*
* Copyright (C) 2009 by Abhishek Kulkarni <adkulkar@umail.iu.edu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#include <linux/jiffies.h>
#include <linux/file.h>
#include <linux/slab.h>
#include <linux/stat.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <net/9p/9p.h>
#include "v9fs.h"
#include "cache.h"
#define CACHETAG_LEN 11
struct fscache_netfs v9fs_cache_netfs = {
.name = "9p",
.version = 0,
};
/**
* v9fs_random_cachetag - Generate a random tag to be associated
* with a new cache session.
*
* The value of jiffies is used for a fairly randomly cache tag.
*/
static
int v9fs_random_cachetag(struct v9fs_session_info *v9ses)
{
v9ses->cachetag = kmalloc(CACHETAG_LEN, GFP_KERNEL);
if (!v9ses->cachetag)
return -ENOMEM;
return scnprintf(v9ses->cachetag, CACHETAG_LEN, "%lu", jiffies);
}
static uint16_t v9fs_cache_session_get_key(const void *cookie_netfs_data,
void *buffer, uint16_t bufmax)
{
struct v9fs_session_info *v9ses;
uint16_t klen = 0;
v9ses = (struct v9fs_session_info *)cookie_netfs_data;
p9_debug(P9_DEBUG_FSC, "session %p buf %p size %u\n",
v9ses, buffer, bufmax);
if (v9ses->cachetag)
klen = strlen(v9ses->cachetag);
if (klen > bufmax)
return 0;
memcpy(buffer, v9ses->cachetag, klen);
p9_debug(P9_DEBUG_FSC, "cache session tag %s\n", v9ses->cachetag);
return klen;
}
const struct fscache_cookie_def v9fs_cache_session_index_def = {
.name = "9P.session",
.type = FSCACHE_COOKIE_TYPE_INDEX,
.get_key = v9fs_cache_session_get_key,
};
void v9fs_cache_session_get_cookie(struct v9fs_session_info *v9ses)
{
/* If no cache session tag was specified, we generate a random one. */
if (!v9ses->cachetag)
v9fs_random_cachetag(v9ses);
v9ses->fscache = fscache_acquire_cookie(v9fs_cache_netfs.primary_index,
&v9fs_cache_session_index_def,
v9ses);
p9_debug(P9_DEBUG_FSC, "session %p get cookie %p\n",
v9ses, v9ses->fscache);
}
void v9fs_cache_session_put_cookie(struct v9fs_session_info *v9ses)
{
p9_debug(P9_DEBUG_FSC, "session %p put cookie %p\n",
v9ses, v9ses->fscache);
fscache_relinquish_cookie(v9ses->fscache, 0);
v9ses->fscache = NULL;
}
static uint16_t v9fs_cache_inode_get_key(const void *cookie_netfs_data,
void *buffer, uint16_t bufmax)
{
const struct v9fs_inode *v9inode = cookie_netfs_data;
memcpy(buffer, &v9inode->qid.path, sizeof(v9inode->qid.path));
p9_debug(P9_DEBUG_FSC, "inode %p get key %llu\n",
&v9inode->vfs_inode, v9inode->qid.path);
return sizeof(v9inode->qid.path);
}
static void v9fs_cache_inode_get_attr(const void *cookie_netfs_data,
uint64_t *size)
{
const struct v9fs_inode *v9inode = cookie_netfs_data;
*size = i_size_read(&v9inode->vfs_inode);
p9_debug(P9_DEBUG_FSC, "inode %p get attr %llu\n",
&v9inode->vfs_inode, *size);
}
static uint16_t v9fs_cache_inode_get_aux(const void *cookie_netfs_data,
void *buffer, uint16_t buflen)
{
const struct v9fs_inode *v9inode = cookie_netfs_data;
memcpy(buffer, &v9inode->qid.version, sizeof(v9inode->qid.version));
p9_debug(P9_DEBUG_FSC, "inode %p get aux %u\n",
&v9inode->vfs_inode, v9inode->qid.version);
return sizeof(v9inode->qid.version);
}
static enum
fscache_checkaux v9fs_cache_inode_check_aux(void *cookie_netfs_data,
const void *buffer,
uint16_t buflen)
{
const struct v9fs_inode *v9inode = cookie_netfs_data;
if (buflen != sizeof(v9inode->qid.version))
return FSCACHE_CHECKAUX_OBSOLETE;
if (memcmp(buffer, &v9inode->qid.version,
sizeof(v9inode->qid.version)))
return FSCACHE_CHECKAUX_OBSOLETE;
return FSCACHE_CHECKAUX_OKAY;
}
static void v9fs_cache_inode_now_uncached(void *cookie_netfs_data)
{
struct v9fs_inode *v9inode = cookie_netfs_data;
struct pagevec pvec;
pgoff_t first;
int loop, nr_pages;
pagevec_init(&pvec, 0);
first = 0;
for (;;) {
nr_pages = pagevec_lookup(&pvec, v9inode->vfs_inode.i_mapping,
first,
PAGEVEC_SIZE - pagevec_count(&pvec));
if (!nr_pages)
break;
for (loop = 0; loop < nr_pages; loop++)
ClearPageFsCache(pvec.pages[loop]);
first = pvec.pages[nr_pages - 1]->index + 1;
pvec.nr = nr_pages;
pagevec_release(&pvec);
cond_resched();
}
}
const struct fscache_cookie_def v9fs_cache_inode_index_def = {
.name = "9p.inode",
.type = FSCACHE_COOKIE_TYPE_DATAFILE,
.get_key = v9fs_cache_inode_get_key,
.get_attr = v9fs_cache_inode_get_attr,
.get_aux = v9fs_cache_inode_get_aux,
.check_aux = v9fs_cache_inode_check_aux,
.now_uncached = v9fs_cache_inode_now_uncached,
};
void v9fs_cache_inode_get_cookie(struct inode *inode)
{
struct v9fs_inode *v9inode;
struct v9fs_session_info *v9ses;
if (!S_ISREG(inode->i_mode))
return;
v9inode = V9FS_I(inode);
if (v9inode->fscache)
return;
v9ses = v9fs_inode2v9ses(inode);
v9inode->fscache = fscache_acquire_cookie(v9ses->fscache,
&v9fs_cache_inode_index_def,
v9inode);
p9_debug(P9_DEBUG_FSC, "inode %p get cookie %p\n",
inode, v9inode->fscache);
}
void v9fs_cache_inode_put_cookie(struct inode *inode)
{
struct v9fs_inode *v9inode = V9FS_I(inode);
if (!v9inode->fscache)
return;
p9_debug(P9_DEBUG_FSC, "inode %p put cookie %p\n",
inode, v9inode->fscache);
fscache_relinquish_cookie(v9inode->fscache, 0);
v9inode->fscache = NULL;
}
void v9fs_cache_inode_flush_cookie(struct inode *inode)
{
struct v9fs_inode *v9inode = V9FS_I(inode);
if (!v9inode->fscache)
return;
p9_debug(P9_DEBUG_FSC, "inode %p flush cookie %p\n",
inode, v9inode->fscache);
fscache_relinquish_cookie(v9inode->fscache, 1);
v9inode->fscache = NULL;
}
void v9fs_cache_inode_set_cookie(struct inode *inode, struct file *filp)
{
struct v9fs_inode *v9inode = V9FS_I(inode);
struct p9_fid *fid;
if (!v9inode->fscache)
return;
spin_lock(&v9inode->fscache_lock);
fid = filp->private_data;
if ((filp->f_flags & O_ACCMODE) != O_RDONLY)
v9fs_cache_inode_flush_cookie(inode);
else
v9fs_cache_inode_get_cookie(inode);
spin_unlock(&v9inode->fscache_lock);
}
void v9fs_cache_inode_reset_cookie(struct inode *inode)
{
struct v9fs_inode *v9inode = V9FS_I(inode);
struct v9fs_session_info *v9ses;
struct fscache_cookie *old;
if (!v9inode->fscache)
return;
old = v9inode->fscache;
spin_lock(&v9inode->fscache_lock);
fscache_relinquish_cookie(v9inode->fscache, 1);
v9ses = v9fs_inode2v9ses(inode);
v9inode->fscache = fscache_acquire_cookie(v9ses->fscache,
&v9fs_cache_inode_index_def,
v9inode);
p9_debug(P9_DEBUG_FSC, "inode %p revalidating cookie old %p new %p\n",
inode, old, v9inode->fscache);
spin_unlock(&v9inode->fscache_lock);
}
int __v9fs_fscache_release_page(struct page *page, gfp_t gfp)
{
struct inode *inode = page->mapping->host;
struct v9fs_inode *v9inode = V9FS_I(inode);
BUG_ON(!v9inode->fscache);
return fscache_maybe_release_page(v9inode->fscache, page, gfp);
}
void __v9fs_fscache_invalidate_page(struct page *page)
{
struct inode *inode = page->mapping->host;
struct v9fs_inode *v9inode = V9FS_I(inode);
BUG_ON(!v9inode->fscache);
if (PageFsCache(page)) {
fscache_wait_on_page_write(v9inode->fscache, page);
BUG_ON(!PageLocked(page));
fscache_uncache_page(v9inode->fscache, page);
}
}
static void v9fs_vfs_readpage_complete(struct page *page, void *data,
int error)
{
if (!error)
SetPageUptodate(page);
unlock_page(page);
}
/**
* __v9fs_readpage_from_fscache - read a page from cache
*
* Returns 0 if the pages are in cache and a BIO is submitted,
* 1 if the pages are not in cache and -error otherwise.
*/
int __v9fs_readpage_from_fscache(struct inode *inode, struct page *page)
{
int ret;
const struct v9fs_inode *v9inode = V9FS_I(inode);
p9_debug(P9_DEBUG_FSC, "inode %p page %p\n", inode, page);
if (!v9inode->fscache)
return -ENOBUFS;
ret = fscache_read_or_alloc_page(v9inode->fscache,
page,
v9fs_vfs_readpage_complete,
NULL,
GFP_KERNEL);
switch (ret) {
case -ENOBUFS:
case -ENODATA:
p9_debug(P9_DEBUG_FSC, "page/inode not in cache %d\n", ret);
return 1;
case 0:
p9_debug(P9_DEBUG_FSC, "BIO submitted\n");
return ret;
default:
p9_debug(P9_DEBUG_FSC, "ret %d\n", ret);
return ret;
}
}
/**
* __v9fs_readpages_from_fscache - read multiple pages from cache
*
* Returns 0 if the pages are in cache and a BIO is submitted,
* 1 if the pages are not in cache and -error otherwise.
*/
int __v9fs_readpages_from_fscache(struct inode *inode,
struct address_space *mapping,
struct list_head *pages,
unsigned *nr_pages)
{
int ret;
const struct v9fs_inode *v9inode = V9FS_I(inode);
p9_debug(P9_DEBUG_FSC, "inode %p pages %u\n", inode, *nr_pages);
if (!v9inode->fscache)
return -ENOBUFS;
ret = fscache_read_or_alloc_pages(v9inode->fscache,
mapping, pages, nr_pages,
v9fs_vfs_readpage_complete,
NULL,
mapping_gfp_mask(mapping));
switch (ret) {
case -ENOBUFS:
case -ENODATA:
p9_debug(P9_DEBUG_FSC, "pages/inodes not in cache %d\n", ret);
return 1;
case 0:
BUG_ON(!list_empty(pages));
BUG_ON(*nr_pages != 0);
p9_debug(P9_DEBUG_FSC, "BIO submitted\n");
return ret;
default:
p9_debug(P9_DEBUG_FSC, "ret %d\n", ret);
return ret;
}
}
/**
* __v9fs_readpage_to_fscache - write a page to the cache
*
*/
void __v9fs_readpage_to_fscache(struct inode *inode, struct page *page)
{
int ret;
const struct v9fs_inode *v9inode = V9FS_I(inode);
p9_debug(P9_DEBUG_FSC, "inode %p page %p\n", inode, page);
ret = fscache_write_page(v9inode->fscache, page, GFP_KERNEL);
p9_debug(P9_DEBUG_FSC, "ret = %d\n", ret);
if (ret != 0)
v9fs_uncache_page(inode, page);
}
/*
* wait for a page to complete writing to the cache
*/
void __v9fs_fscache_wait_on_page_write(struct inode *inode, struct page *page)
{
const struct v9fs_inode *v9inode = V9FS_I(inode);
p9_debug(P9_DEBUG_FSC, "inode %p page %p\n", inode, page);
if (PageFsCache(page))
fscache_wait_on_page_write(v9inode->fscache, page);
}

View File

@@ -0,0 +1,139 @@
/*
* V9FS cache definitions.
*
* Copyright (C) 2009 by Abhishek Kulkarni <adkulkar@umail.iu.edu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#ifndef _9P_CACHE_H
#ifdef CONFIG_9P_FSCACHE
#include <linux/fscache.h>
#include <linux/spinlock.h>
extern struct fscache_netfs v9fs_cache_netfs;
extern const struct fscache_cookie_def v9fs_cache_session_index_def;
extern const struct fscache_cookie_def v9fs_cache_inode_index_def;
extern void v9fs_cache_session_get_cookie(struct v9fs_session_info *v9ses);
extern void v9fs_cache_session_put_cookie(struct v9fs_session_info *v9ses);
extern void v9fs_cache_inode_get_cookie(struct inode *inode);
extern void v9fs_cache_inode_put_cookie(struct inode *inode);
extern void v9fs_cache_inode_flush_cookie(struct inode *inode);
extern void v9fs_cache_inode_set_cookie(struct inode *inode, struct file *filp);
extern void v9fs_cache_inode_reset_cookie(struct inode *inode);
extern int __v9fs_cache_register(void);
extern void __v9fs_cache_unregister(void);
extern int __v9fs_fscache_release_page(struct page *page, gfp_t gfp);
extern void __v9fs_fscache_invalidate_page(struct page *page);
extern int __v9fs_readpage_from_fscache(struct inode *inode,
struct page *page);
extern int __v9fs_readpages_from_fscache(struct inode *inode,
struct address_space *mapping,
struct list_head *pages,
unsigned *nr_pages);
extern void __v9fs_readpage_to_fscache(struct inode *inode, struct page *page);
extern void __v9fs_fscache_wait_on_page_write(struct inode *inode,
struct page *page);
static inline int v9fs_fscache_release_page(struct page *page,
gfp_t gfp)
{
return __v9fs_fscache_release_page(page, gfp);
}
static inline void v9fs_fscache_invalidate_page(struct page *page)
{
__v9fs_fscache_invalidate_page(page);
}
static inline int v9fs_readpage_from_fscache(struct inode *inode,
struct page *page)
{
return __v9fs_readpage_from_fscache(inode, page);
}
static inline int v9fs_readpages_from_fscache(struct inode *inode,
struct address_space *mapping,
struct list_head *pages,
unsigned *nr_pages)
{
return __v9fs_readpages_from_fscache(inode, mapping, pages,
nr_pages);
}
static inline void v9fs_readpage_to_fscache(struct inode *inode,
struct page *page)
{
if (PageFsCache(page))
__v9fs_readpage_to_fscache(inode, page);
}
static inline void v9fs_uncache_page(struct inode *inode, struct page *page)
{
struct v9fs_inode *v9inode = V9FS_I(inode);
fscache_uncache_page(v9inode->fscache, page);
BUG_ON(PageFsCache(page));
}
static inline void v9fs_fscache_wait_on_page_write(struct inode *inode,
struct page *page)
{
return __v9fs_fscache_wait_on_page_write(inode, page);
}
#else /* CONFIG_9P_FSCACHE */
static inline int v9fs_fscache_release_page(struct page *page,
gfp_t gfp) {
return 1;
}
static inline void v9fs_fscache_invalidate_page(struct page *page) {}
static inline int v9fs_readpage_from_fscache(struct inode *inode,
struct page *page)
{
return -ENOBUFS;
}
static inline int v9fs_readpages_from_fscache(struct inode *inode,
struct address_space *mapping,
struct list_head *pages,
unsigned *nr_pages)
{
return -ENOBUFS;
}
static inline void v9fs_readpage_to_fscache(struct inode *inode,
struct page *page)
{}
static inline void v9fs_uncache_page(struct inode *inode, struct page *page)
{}
static inline void v9fs_fscache_wait_on_page_write(struct inode *inode,
struct page *page)
{
return;
}
#endif /* CONFIG_9P_FSCACHE */
#endif /* _9P_CACHE_H */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,247 @@
/*
* linux/fs/9p/error.c
*
* Error string handling
*
* Plan 9 uses error strings, Unix uses error numbers. These functions
* try to help manage that and provide for dynamically adding error
* mappings.
*
* Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
* Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/list.h>
#include <linux/jhash.h>
#include <linux/errno.h>
#include <net/9p/9p.h>
/**
* struct errormap - map string errors from Plan 9 to Linux numeric ids
* @name: string sent over 9P
* @val: numeric id most closely representing @name
* @namelen: length of string
* @list: hash-table list for string lookup
*/
struct errormap {
char *name;
int val;
int namelen;
struct hlist_node list;
};
#define ERRHASHSZ 32
static struct hlist_head hash_errmap[ERRHASHSZ];
/* FixMe - reduce to a reasonable size */
static struct errormap errmap[] = {
{"Operation not permitted", EPERM},
{"wstat prohibited", EPERM},
{"No such file or directory", ENOENT},
{"directory entry not found", ENOENT},
{"file not found", ENOENT},
{"Interrupted system call", EINTR},
{"Input/output error", EIO},
{"No such device or address", ENXIO},
{"Argument list too long", E2BIG},
{"Bad file descriptor", EBADF},
{"Resource temporarily unavailable", EAGAIN},
{"Cannot allocate memory", ENOMEM},
{"Permission denied", EACCES},
{"Bad address", EFAULT},
{"Block device required", ENOTBLK},
{"Device or resource busy", EBUSY},
{"File exists", EEXIST},
{"Invalid cross-device link", EXDEV},
{"No such device", ENODEV},
{"Not a directory", ENOTDIR},
{"Is a directory", EISDIR},
{"Invalid argument", EINVAL},
{"Too many open files in system", ENFILE},
{"Too many open files", EMFILE},
{"Text file busy", ETXTBSY},
{"File too large", EFBIG},
{"No space left on device", ENOSPC},
{"Illegal seek", ESPIPE},
{"Read-only file system", EROFS},
{"Too many links", EMLINK},
{"Broken pipe", EPIPE},
{"Numerical argument out of domain", EDOM},
{"Numerical result out of range", ERANGE},
{"Resource deadlock avoided", EDEADLK},
{"File name too long", ENAMETOOLONG},
{"No locks available", ENOLCK},
{"Function not implemented", ENOSYS},
{"Directory not empty", ENOTEMPTY},
{"Too many levels of symbolic links", ELOOP},
{"No message of desired type", ENOMSG},
{"Identifier removed", EIDRM},
{"No data available", ENODATA},
{"Machine is not on the network", ENONET},
{"Package not installed", ENOPKG},
{"Object is remote", EREMOTE},
{"Link has been severed", ENOLINK},
{"Communication error on send", ECOMM},
{"Protocol error", EPROTO},
{"Bad message", EBADMSG},
{"File descriptor in bad state", EBADFD},
{"Streams pipe error", ESTRPIPE},
{"Too many users", EUSERS},
{"Socket operation on non-socket", ENOTSOCK},
{"Message too long", EMSGSIZE},
{"Protocol not available", ENOPROTOOPT},
{"Protocol not supported", EPROTONOSUPPORT},
{"Socket type not supported", ESOCKTNOSUPPORT},
{"Operation not supported", EOPNOTSUPP},
{"Protocol family not supported", EPFNOSUPPORT},
{"Network is down", ENETDOWN},
{"Network is unreachable", ENETUNREACH},
{"Network dropped connection on reset", ENETRESET},
{"Software caused connection abort", ECONNABORTED},
{"Connection reset by peer", ECONNRESET},
{"No buffer space available", ENOBUFS},
{"Transport endpoint is already connected", EISCONN},
{"Transport endpoint is not connected", ENOTCONN},
{"Cannot send after transport endpoint shutdown", ESHUTDOWN},
{"Connection timed out", ETIMEDOUT},
{"Connection refused", ECONNREFUSED},
{"Host is down", EHOSTDOWN},
{"No route to host", EHOSTUNREACH},
{"Operation already in progress", EALREADY},
{"Operation now in progress", EINPROGRESS},
{"Is a named type file", EISNAM},
{"Remote I/O error", EREMOTEIO},
{"Disk quota exceeded", EDQUOT},
/* errors from fossil, vacfs, and u9fs */
{"fid unknown or out of range", EBADF},
{"permission denied", EACCES},
{"file does not exist", ENOENT},
{"authentication failed", ECONNREFUSED},
{"bad offset in directory read", ESPIPE},
{"bad use of fid", EBADF},
{"wstat can't convert between files and directories", EPERM},
{"directory is not empty", ENOTEMPTY},
{"file exists", EEXIST},
{"file already exists", EEXIST},
{"file or directory already exists", EEXIST},
{"fid already in use", EBADF},
{"file in use", ETXTBSY},
{"i/o error", EIO},
{"file already open for I/O", ETXTBSY},
{"illegal mode", EINVAL},
{"illegal name", ENAMETOOLONG},
{"not a directory", ENOTDIR},
{"not a member of proposed group", EPERM},
{"not owner", EACCES},
{"only owner can change group in wstat", EACCES},
{"read only file system", EROFS},
{"no access to special file", EPERM},
{"i/o count too large", EIO},
{"unknown group", EINVAL},
{"unknown user", EINVAL},
{"bogus wstat buffer", EPROTO},
{"exclusive use file already open", EAGAIN},
{"corrupted directory entry", EIO},
{"corrupted file entry", EIO},
{"corrupted block label", EIO},
{"corrupted meta data", EIO},
{"illegal offset", EINVAL},
{"illegal path element", ENOENT},
{"root of file system is corrupted", EIO},
{"corrupted super block", EIO},
{"protocol botch", EPROTO},
{"file system is full", ENOSPC},
{"file is in use", EAGAIN},
{"directory entry is not allocated", ENOENT},
{"file is read only", EROFS},
{"file has been removed", EIDRM},
{"only support truncation to zero length", EPERM},
{"cannot remove root", EPERM},
{"file too big", EFBIG},
{"venti i/o error", EIO},
/* these are not errors */
{"u9fs rhostsauth: no authentication required", 0},
{"u9fs authnone: no authentication required", 0},
{NULL, -1}
};
/**
* p9_error_init - preload mappings into hash list
*
*/
int p9_error_init(void)
{
struct errormap *c;
int bucket;
/* initialize hash table */
for (bucket = 0; bucket < ERRHASHSZ; bucket++)
INIT_HLIST_HEAD(&hash_errmap[bucket]);
/* load initial error map into hash table */
for (c = errmap; c->name != NULL; c++) {
c->namelen = strlen(c->name);
bucket = jhash(c->name, c->namelen, 0) % ERRHASHSZ;
INIT_HLIST_NODE(&c->list);
hlist_add_head(&c->list, &hash_errmap[bucket]);
}
return 1;
}
EXPORT_SYMBOL(p9_error_init);
/**
* errstr2errno - convert error string to error number
* @errstr: error string
* @len: length of error string
*
*/
int p9_errstr2errno(char *errstr, int len)
{
int errno;
struct errormap *c;
int bucket;
errno = 0;
c = NULL;
bucket = jhash(errstr, len, 0) % ERRHASHSZ;
hlist_for_each_entry(c, &hash_errmap[bucket], list) {
if (c->namelen == len && !memcmp(c->name, errstr, len)) {
errno = c->val;
break;
}
}
if (errno == 0) {
/* TODO: if error isn't found, add it dynamically */
errstr[len] = 0;
pr_err("%s: server reported unknown error %s\n",
__func__, errstr);
errno = ESERVERFAULT;
}
return -errno;
}
EXPORT_SYMBOL(p9_errstr2errno);

View File

@@ -0,0 +1,306 @@
/*
* V9FS FID Management
*
* Copyright (C) 2007 by Latchesar Ionkov <lucho@ionkov.net>
* Copyright (C) 2005, 2006 by Eric Van Hensbergen <ericvh@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/idr.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
#include "v9fs.h"
#include "v9fs_vfs.h"
#include "fid.h"
/**
* v9fs_fid_add - add a fid to a dentry
* @dentry: dentry that the fid is being added to
* @fid: fid to add
*
*/
static inline void __add_fid(struct dentry *dentry, struct p9_fid *fid)
{
hlist_add_head(&fid->dlist, (struct hlist_head *)&dentry->d_fsdata);
}
void v9fs_fid_add(struct dentry *dentry, struct p9_fid *fid)
{
spin_lock(&dentry->d_lock);
__add_fid(dentry, fid);
spin_unlock(&dentry->d_lock);
}
/**
* v9fs_fid_find - retrieve a fid that belongs to the specified uid
* @dentry: dentry to look for fid in
* @uid: return fid that belongs to the specified user
* @any: if non-zero, return any fid associated with the dentry
*
*/
static struct p9_fid *v9fs_fid_find(struct dentry *dentry, kuid_t uid, int any)
{
struct p9_fid *fid, *ret;
p9_debug(P9_DEBUG_VFS, " dentry: %s (%p) uid %d any %d\n",
dentry->d_name.name, dentry, from_kuid(&init_user_ns, uid),
any);
ret = NULL;
/* we'll recheck under lock if there's anything to look in */
if (dentry->d_fsdata) {
struct hlist_head *h = (struct hlist_head *)&dentry->d_fsdata;
spin_lock(&dentry->d_lock);
hlist_for_each_entry(fid, h, dlist) {
if (any || uid_eq(fid->uid, uid)) {
ret = fid;
break;
}
}
spin_unlock(&dentry->d_lock);
}
return ret;
}
/*
* We need to hold v9ses->rename_sem as long as we hold references
* to returned path array. Array element contain pointers to
* dentry names.
*/
static int build_path_from_dentry(struct v9fs_session_info *v9ses,
struct dentry *dentry, char ***names)
{
int n = 0, i;
char **wnames;
struct dentry *ds;
for (ds = dentry; !IS_ROOT(ds); ds = ds->d_parent)
n++;
wnames = kmalloc(sizeof(char *) * n, GFP_KERNEL);
if (!wnames)
goto err_out;
for (ds = dentry, i = (n-1); i >= 0; i--, ds = ds->d_parent)
wnames[i] = (char *)ds->d_name.name;
*names = wnames;
return n;
err_out:
return -ENOMEM;
}
static struct p9_fid *v9fs_fid_lookup_with_uid(struct dentry *dentry,
kuid_t uid, int any)
{
struct dentry *ds;
char **wnames, *uname;
int i, n, l, clone, access;
struct v9fs_session_info *v9ses;
struct p9_fid *fid, *old_fid = NULL;
v9ses = v9fs_dentry2v9ses(dentry);
access = v9ses->flags & V9FS_ACCESS_MASK;
fid = v9fs_fid_find(dentry, uid, any);
if (fid)
return fid;
/*
* we don't have a matching fid. To do a TWALK we need
* parent fid. We need to prevent rename when we want to
* look at the parent.
*/
down_read(&v9ses->rename_sem);
ds = dentry->d_parent;
fid = v9fs_fid_find(ds, uid, any);
if (fid) {
/* Found the parent fid do a lookup with that */
fid = p9_client_walk(fid, 1, (char **)&dentry->d_name.name, 1);
goto fid_out;
}
up_read(&v9ses->rename_sem);
/* start from the root and try to do a lookup */
fid = v9fs_fid_find(dentry->d_sb->s_root, uid, any);
if (!fid) {
/* the user is not attached to the fs yet */
if (access == V9FS_ACCESS_SINGLE)
return ERR_PTR(-EPERM);
if (v9fs_proto_dotu(v9ses) || v9fs_proto_dotl(v9ses))
uname = NULL;
else
uname = v9ses->uname;
fid = p9_client_attach(v9ses->clnt, NULL, uname, uid,
v9ses->aname);
if (IS_ERR(fid))
return fid;
v9fs_fid_add(dentry->d_sb->s_root, fid);
}
/* If we are root ourself just return that */
if (dentry->d_sb->s_root == dentry)
return fid;
/*
* Do a multipath walk with attached root.
* When walking parent we need to make sure we
* don't have a parallel rename happening
*/
down_read(&v9ses->rename_sem);
n = build_path_from_dentry(v9ses, dentry, &wnames);
if (n < 0) {
fid = ERR_PTR(n);
goto err_out;
}
clone = 1;
i = 0;
while (i < n) {
l = min(n - i, P9_MAXWELEM);
/*
* We need to hold rename lock when doing a multipath
* walk to ensure none of the patch component change
*/
fid = p9_client_walk(fid, l, &wnames[i], clone);
if (IS_ERR(fid)) {
if (old_fid) {
/*
* If we fail, clunk fid which are mapping
* to path component and not the last component
* of the path.
*/
p9_client_clunk(old_fid);
}
kfree(wnames);
goto err_out;
}
old_fid = fid;
i += l;
clone = 0;
}
kfree(wnames);
fid_out:
if (!IS_ERR(fid)) {
spin_lock(&dentry->d_lock);
if (d_unhashed(dentry)) {
spin_unlock(&dentry->d_lock);
p9_client_clunk(fid);
fid = ERR_PTR(-ENOENT);
} else {
__add_fid(dentry, fid);
spin_unlock(&dentry->d_lock);
}
}
err_out:
up_read(&v9ses->rename_sem);
return fid;
}
/**
* v9fs_fid_lookup - lookup for a fid, try to walk if not found
* @dentry: dentry to look for fid in
*
* Look for a fid in the specified dentry for the current user.
* If no fid is found, try to create one walking from a fid from the parent
* dentry (if it has one), or the root dentry. If the user haven't accessed
* the fs yet, attach now and walk from the root.
*/
struct p9_fid *v9fs_fid_lookup(struct dentry *dentry)
{
kuid_t uid;
int any, access;
struct v9fs_session_info *v9ses;
v9ses = v9fs_dentry2v9ses(dentry);
access = v9ses->flags & V9FS_ACCESS_MASK;
switch (access) {
case V9FS_ACCESS_SINGLE:
case V9FS_ACCESS_USER:
case V9FS_ACCESS_CLIENT:
uid = current_fsuid();
any = 0;
break;
case V9FS_ACCESS_ANY:
uid = v9ses->uid;
any = 1;
break;
default:
uid = INVALID_UID;
any = 0;
break;
}
return v9fs_fid_lookup_with_uid(dentry, uid, any);
}
struct p9_fid *v9fs_fid_clone(struct dentry *dentry)
{
struct p9_fid *fid, *ret;
fid = v9fs_fid_lookup(dentry);
if (IS_ERR(fid))
return fid;
ret = p9_client_walk(fid, 0, NULL, 1);
return ret;
}
static struct p9_fid *v9fs_fid_clone_with_uid(struct dentry *dentry, kuid_t uid)
{
struct p9_fid *fid, *ret;
fid = v9fs_fid_lookup_with_uid(dentry, uid, 0);
if (IS_ERR(fid))
return fid;
ret = p9_client_walk(fid, 0, NULL, 1);
return ret;
}
struct p9_fid *v9fs_writeback_fid(struct dentry *dentry)
{
int err;
struct p9_fid *fid;
fid = v9fs_fid_clone_with_uid(dentry, GLOBAL_ROOT_UID);
if (IS_ERR(fid))
goto error_out;
/*
* writeback fid will only be used to write back the
* dirty pages. We always request for the open fid in read-write
* mode so that a partial page write which result in page
* read can work.
*/
err = p9_client_open(fid, O_RDWR);
if (err < 0) {
p9_client_clunk(fid);
fid = ERR_PTR(err);
goto error_out;
}
error_out:
return fid;
}

View File

@@ -0,0 +1,30 @@
/*
* V9FS FID Management
*
* Copyright (C) 2005 by Eric Van Hensbergen <ericvh@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#ifndef FS_9P_FID_H
#define FS_9P_FID_H
#include <linux/list.h>
struct p9_fid *v9fs_fid_lookup(struct dentry *dentry);
struct p9_fid *v9fs_fid_clone(struct dentry *dentry);
void v9fs_fid_add(struct dentry *dentry, struct p9_fid *fid);
struct p9_fid *v9fs_writeback_fid(struct dentry *dentry);
#endif

View File

@@ -0,0 +1,201 @@
/*
* net/9p/9p.c
*
* 9P entry point
*
* Copyright (C) 2007 by Latchesar Ionkov <lucho@ionkov.net>
* Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
* Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/moduleparam.h>
#include <net/9p/9p.h>
#include <linux/fs.h>
#include <linux/parser.h>
#include <net/9p/client.h>
#include <net/9p/transport.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#ifdef CONFIG_NET_9P_DEBUG
unsigned int p9_debug_level = 0; /* feature-rific global debug level */
EXPORT_SYMBOL(p9_debug_level);
module_param_named(debug, p9_debug_level, uint, 0);
MODULE_PARM_DESC(debug, "9P debugging level");
void _p9_debug(enum p9_debug_flags level, const char *func,
const char *fmt, ...)
{
struct va_format vaf;
va_list args;
if ((p9_debug_level & level) != level)
return;
va_start(args, fmt);
vaf.fmt = fmt;
vaf.va = &args;
if (level == P9_DEBUG_9P)
pr_notice("(%8.8d) %pV", task_pid_nr(current), &vaf);
else
pr_notice("-- %s (%d): %pV", func, task_pid_nr(current), &vaf);
va_end(args);
}
EXPORT_SYMBOL(_p9_debug);
#endif
/*
* Dynamic Transport Registration Routines
*
*/
static DEFINE_SPINLOCK(v9fs_trans_lock);
static LIST_HEAD(v9fs_trans_list);
/**
* v9fs_register_trans - register a new transport with 9p
* @m: structure describing the transport module and entry points
*
*/
void v9fs_register_trans(struct p9_trans_module *m)
{
spin_lock(&v9fs_trans_lock);
list_add_tail(&m->list, &v9fs_trans_list);
spin_unlock(&v9fs_trans_lock);
}
EXPORT_SYMBOL(v9fs_register_trans);
/**
* v9fs_unregister_trans - unregister a 9p transport
* @m: the transport to remove
*
*/
void v9fs_unregister_trans(struct p9_trans_module *m)
{
spin_lock(&v9fs_trans_lock);
list_del_init(&m->list);
spin_unlock(&v9fs_trans_lock);
}
EXPORT_SYMBOL(v9fs_unregister_trans);
/**
* v9fs_get_trans_by_name - get transport with the matching name
* @name: string identifying transport
*
*/
struct p9_trans_module *v9fs_get_trans_by_name(char *s)
{
struct p9_trans_module *t, *found = NULL;
spin_lock(&v9fs_trans_lock);
list_for_each_entry(t, &v9fs_trans_list, list)
if (strcmp(t->name, s) == 0 &&
try_module_get(t->owner)) {
found = t;
break;
}
spin_unlock(&v9fs_trans_lock);
return found;
}
EXPORT_SYMBOL(v9fs_get_trans_by_name);
/**
* v9fs_get_default_trans - get the default transport
*
*/
struct p9_trans_module *v9fs_get_default_trans(void)
{
struct p9_trans_module *t, *found = NULL;
spin_lock(&v9fs_trans_lock);
list_for_each_entry(t, &v9fs_trans_list, list)
if (t->def && try_module_get(t->owner)) {
found = t;
break;
}
if (!found)
list_for_each_entry(t, &v9fs_trans_list, list)
if (try_module_get(t->owner)) {
found = t;
break;
}
spin_unlock(&v9fs_trans_lock);
return found;
}
EXPORT_SYMBOL(v9fs_get_default_trans);
/**
* v9fs_put_trans - put trans
* @m: transport to put
*
*/
void v9fs_put_trans(struct p9_trans_module *m)
{
if (m)
module_put(m->owner);
}
/**
* init_p9 - Initialize module
*
*/
static int __init init_p9(void)
{
int ret = 0;
p9_error_init();
pr_info("Installing 9P2000 support\n");
p9_trans_fd_init();
return ret;
}
/**
* exit_p9 - shutdown module
*
*/
static void __exit exit_p9(void)
{
pr_info("Unloading 9P2000 support\n");
p9_trans_fd_exit();
}
module_init(init_p9)
module_exit(exit_p9)
MODULE_AUTHOR("Latchesar Ionkov <lucho@ionkov.net>");
MODULE_AUTHOR("Eric Van Hensbergen <ericvh@gmail.com>");
MODULE_AUTHOR("Ron Minnich <rminnich@lanl.gov>");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,636 @@
/*
* net/9p/protocol.c
*
* 9P Protocol Support Code
*
* Copyright (C) 2008 by Eric Van Hensbergen <ericvh@gmail.com>
*
* Base on code from Anthony Liguori <aliguori@us.ibm.com>
* Copyright (C) 2008 by IBM, Corp.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/stddef.h>
#include <linux/types.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
#include "protocol.h"
#include <trace/events/9p.h>
static int
p9pdu_writef(struct p9_fcall *pdu, int proto_version, const char *fmt, ...);
void p9stat_free(struct p9_wstat *stbuf)
{
kfree(stbuf->name);
kfree(stbuf->uid);
kfree(stbuf->gid);
kfree(stbuf->muid);
kfree(stbuf->extension);
}
EXPORT_SYMBOL(p9stat_free);
size_t pdu_read(struct p9_fcall *pdu, void *data, size_t size)
{
size_t len = min(pdu->size - pdu->offset, size);
memcpy(data, &pdu->sdata[pdu->offset], len);
pdu->offset += len;
return size - len;
}
static size_t pdu_write(struct p9_fcall *pdu, const void *data, size_t size)
{
size_t len = min(pdu->capacity - pdu->size, size);
memcpy(&pdu->sdata[pdu->size], data, len);
pdu->size += len;
return size - len;
}
static size_t
pdu_write_u(struct p9_fcall *pdu, const char __user *udata, size_t size)
{
size_t len = min(pdu->capacity - pdu->size, size);
if (copy_from_user(&pdu->sdata[pdu->size], udata, len))
len = 0;
pdu->size += len;
return size - len;
}
/*
b - int8_t
w - int16_t
d - int32_t
q - int64_t
s - string
u - numeric uid
g - numeric gid
S - stat
Q - qid
D - data blob (int32_t size followed by void *, results are not freed)
T - array of strings (int16_t count, followed by strings)
R - array of qids (int16_t count, followed by qids)
A - stat for 9p2000.L (p9_stat_dotl)
? - if optional = 1, continue parsing
*/
static int
p9pdu_vreadf(struct p9_fcall *pdu, int proto_version, const char *fmt,
va_list ap)
{
const char *ptr;
int errcode = 0;
for (ptr = fmt; *ptr; ptr++) {
switch (*ptr) {
case 'b':{
int8_t *val = va_arg(ap, int8_t *);
if (pdu_read(pdu, val, sizeof(*val))) {
errcode = -EFAULT;
break;
}
}
break;
case 'w':{
int16_t *val = va_arg(ap, int16_t *);
__le16 le_val;
if (pdu_read(pdu, &le_val, sizeof(le_val))) {
errcode = -EFAULT;
break;
}
*val = le16_to_cpu(le_val);
}
break;
case 'd':{
int32_t *val = va_arg(ap, int32_t *);
__le32 le_val;
if (pdu_read(pdu, &le_val, sizeof(le_val))) {
errcode = -EFAULT;
break;
}
*val = le32_to_cpu(le_val);
}
break;
case 'q':{
int64_t *val = va_arg(ap, int64_t *);
__le64 le_val;
if (pdu_read(pdu, &le_val, sizeof(le_val))) {
errcode = -EFAULT;
break;
}
*val = le64_to_cpu(le_val);
}
break;
case 's':{
char **sptr = va_arg(ap, char **);
uint16_t len;
errcode = p9pdu_readf(pdu, proto_version,
"w", &len);
if (errcode)
break;
*sptr = kmalloc(len + 1, GFP_NOFS);
if (*sptr == NULL) {
errcode = -EFAULT;
break;
}
if (pdu_read(pdu, *sptr, len)) {
errcode = -EFAULT;
kfree(*sptr);
*sptr = NULL;
} else
(*sptr)[len] = 0;
}
break;
case 'u': {
kuid_t *uid = va_arg(ap, kuid_t *);
__le32 le_val;
if (pdu_read(pdu, &le_val, sizeof(le_val))) {
errcode = -EFAULT;
break;
}
*uid = make_kuid(&init_user_ns,
le32_to_cpu(le_val));
} break;
case 'g': {
kgid_t *gid = va_arg(ap, kgid_t *);
__le32 le_val;
if (pdu_read(pdu, &le_val, sizeof(le_val))) {
errcode = -EFAULT;
break;
}
*gid = make_kgid(&init_user_ns,
le32_to_cpu(le_val));
} break;
case 'Q':{
struct p9_qid *qid =
va_arg(ap, struct p9_qid *);
errcode = p9pdu_readf(pdu, proto_version, "bdq",
&qid->type, &qid->version,
&qid->path);
}
break;
case 'S':{
struct p9_wstat *stbuf =
va_arg(ap, struct p9_wstat *);
memset(stbuf, 0, sizeof(struct p9_wstat));
stbuf->n_uid = stbuf->n_muid = INVALID_UID;
stbuf->n_gid = INVALID_GID;
errcode =
p9pdu_readf(pdu, proto_version,
"wwdQdddqssss?sugu",
&stbuf->size, &stbuf->type,
&stbuf->dev, &stbuf->qid,
&stbuf->mode, &stbuf->atime,
&stbuf->mtime, &stbuf->length,
&stbuf->name, &stbuf->uid,
&stbuf->gid, &stbuf->muid,
&stbuf->extension,
&stbuf->n_uid, &stbuf->n_gid,
&stbuf->n_muid);
if (errcode)
p9stat_free(stbuf);
}
break;
case 'D':{
uint32_t *count = va_arg(ap, uint32_t *);
void **data = va_arg(ap, void **);
errcode =
p9pdu_readf(pdu, proto_version, "d", count);
if (!errcode) {
*count =
min_t(uint32_t, *count,
pdu->size - pdu->offset);
*data = &pdu->sdata[pdu->offset];
}
}
break;
case 'T':{
uint16_t *nwname = va_arg(ap, uint16_t *);
char ***wnames = va_arg(ap, char ***);
errcode = p9pdu_readf(pdu, proto_version,
"w", nwname);
if (!errcode) {
*wnames =
kmalloc(sizeof(char *) * *nwname,
GFP_NOFS);
if (!*wnames)
errcode = -ENOMEM;
}
if (!errcode) {
int i;
for (i = 0; i < *nwname; i++) {
errcode =
p9pdu_readf(pdu,
proto_version,
"s",
&(*wnames)[i]);
if (errcode)
break;
}
}
if (errcode) {
if (*wnames) {
int i;
for (i = 0; i < *nwname; i++)
kfree((*wnames)[i]);
}
kfree(*wnames);
*wnames = NULL;
}
}
break;
case 'R':{
int16_t *nwqid = va_arg(ap, int16_t *);
struct p9_qid **wqids =
va_arg(ap, struct p9_qid **);
*wqids = NULL;
errcode =
p9pdu_readf(pdu, proto_version, "w", nwqid);
if (!errcode) {
*wqids =
kmalloc(*nwqid *
sizeof(struct p9_qid),
GFP_NOFS);
if (*wqids == NULL)
errcode = -ENOMEM;
}
if (!errcode) {
int i;
for (i = 0; i < *nwqid; i++) {
errcode =
p9pdu_readf(pdu,
proto_version,
"Q",
&(*wqids)[i]);
if (errcode)
break;
}
}
if (errcode) {
kfree(*wqids);
*wqids = NULL;
}
}
break;
case 'A': {
struct p9_stat_dotl *stbuf =
va_arg(ap, struct p9_stat_dotl *);
memset(stbuf, 0, sizeof(struct p9_stat_dotl));
errcode =
p9pdu_readf(pdu, proto_version,
"qQdugqqqqqqqqqqqqqqq",
&stbuf->st_result_mask,
&stbuf->qid,
&stbuf->st_mode,
&stbuf->st_uid, &stbuf->st_gid,
&stbuf->st_nlink,
&stbuf->st_rdev, &stbuf->st_size,
&stbuf->st_blksize, &stbuf->st_blocks,
&stbuf->st_atime_sec,
&stbuf->st_atime_nsec,
&stbuf->st_mtime_sec,
&stbuf->st_mtime_nsec,
&stbuf->st_ctime_sec,
&stbuf->st_ctime_nsec,
&stbuf->st_btime_sec,
&stbuf->st_btime_nsec,
&stbuf->st_gen,
&stbuf->st_data_version);
}
break;
case '?':
if ((proto_version != p9_proto_2000u) &&
(proto_version != p9_proto_2000L))
return 0;
break;
default:
BUG();
break;
}
if (errcode)
break;
}
return errcode;
}
int
p9pdu_vwritef(struct p9_fcall *pdu, int proto_version, const char *fmt,
va_list ap)
{
const char *ptr;
int errcode = 0;
for (ptr = fmt; *ptr; ptr++) {
switch (*ptr) {
case 'b':{
int8_t val = va_arg(ap, int);
if (pdu_write(pdu, &val, sizeof(val)))
errcode = -EFAULT;
}
break;
case 'w':{
__le16 val = cpu_to_le16(va_arg(ap, int));
if (pdu_write(pdu, &val, sizeof(val)))
errcode = -EFAULT;
}
break;
case 'd':{
__le32 val = cpu_to_le32(va_arg(ap, int32_t));
if (pdu_write(pdu, &val, sizeof(val)))
errcode = -EFAULT;
}
break;
case 'q':{
__le64 val = cpu_to_le64(va_arg(ap, int64_t));
if (pdu_write(pdu, &val, sizeof(val)))
errcode = -EFAULT;
}
break;
case 's':{
const char *sptr = va_arg(ap, const char *);
uint16_t len = 0;
if (sptr)
len = min_t(size_t, strlen(sptr),
USHRT_MAX);
errcode = p9pdu_writef(pdu, proto_version,
"w", len);
if (!errcode && pdu_write(pdu, sptr, len))
errcode = -EFAULT;
}
break;
case 'u': {
kuid_t uid = va_arg(ap, kuid_t);
__le32 val = cpu_to_le32(
from_kuid(&init_user_ns, uid));
if (pdu_write(pdu, &val, sizeof(val)))
errcode = -EFAULT;
} break;
case 'g': {
kgid_t gid = va_arg(ap, kgid_t);
__le32 val = cpu_to_le32(
from_kgid(&init_user_ns, gid));
if (pdu_write(pdu, &val, sizeof(val)))
errcode = -EFAULT;
} break;
case 'Q':{
const struct p9_qid *qid =
va_arg(ap, const struct p9_qid *);
errcode =
p9pdu_writef(pdu, proto_version, "bdq",
qid->type, qid->version,
qid->path);
} break;
case 'S':{
const struct p9_wstat *stbuf =
va_arg(ap, const struct p9_wstat *);
errcode =
p9pdu_writef(pdu, proto_version,
"wwdQdddqssss?sugu",
stbuf->size, stbuf->type,
stbuf->dev, &stbuf->qid,
stbuf->mode, stbuf->atime,
stbuf->mtime, stbuf->length,
stbuf->name, stbuf->uid,
stbuf->gid, stbuf->muid,
stbuf->extension, stbuf->n_uid,
stbuf->n_gid, stbuf->n_muid);
} break;
case 'D':{
uint32_t count = va_arg(ap, uint32_t);
const void *data = va_arg(ap, const void *);
errcode = p9pdu_writef(pdu, proto_version, "d",
count);
if (!errcode && pdu_write(pdu, data, count))
errcode = -EFAULT;
}
break;
case 'U':{
int32_t count = va_arg(ap, int32_t);
const char __user *udata =
va_arg(ap, const void __user *);
errcode = p9pdu_writef(pdu, proto_version, "d",
count);
if (!errcode && pdu_write_u(pdu, udata, count))
errcode = -EFAULT;
}
break;
case 'T':{
uint16_t nwname = va_arg(ap, int);
const char **wnames = va_arg(ap, const char **);
errcode = p9pdu_writef(pdu, proto_version, "w",
nwname);
if (!errcode) {
int i;
for (i = 0; i < nwname; i++) {
errcode =
p9pdu_writef(pdu,
proto_version,
"s",
wnames[i]);
if (errcode)
break;
}
}
}
break;
case 'R':{
int16_t nwqid = va_arg(ap, int);
struct p9_qid *wqids =
va_arg(ap, struct p9_qid *);
errcode = p9pdu_writef(pdu, proto_version, "w",
nwqid);
if (!errcode) {
int i;
for (i = 0; i < nwqid; i++) {
errcode =
p9pdu_writef(pdu,
proto_version,
"Q",
&wqids[i]);
if (errcode)
break;
}
}
}
break;
case 'I':{
struct p9_iattr_dotl *p9attr = va_arg(ap,
struct p9_iattr_dotl *);
errcode = p9pdu_writef(pdu, proto_version,
"ddugqqqqq",
p9attr->valid,
p9attr->mode,
p9attr->uid,
p9attr->gid,
p9attr->size,
p9attr->atime_sec,
p9attr->atime_nsec,
p9attr->mtime_sec,
p9attr->mtime_nsec);
}
break;
case '?':
if ((proto_version != p9_proto_2000u) &&
(proto_version != p9_proto_2000L))
return 0;
break;
default:
BUG();
break;
}
if (errcode)
break;
}
return errcode;
}
int p9pdu_readf(struct p9_fcall *pdu, int proto_version, const char *fmt, ...)
{
va_list ap;
int ret;
va_start(ap, fmt);
ret = p9pdu_vreadf(pdu, proto_version, fmt, ap);
va_end(ap);
return ret;
}
static int
p9pdu_writef(struct p9_fcall *pdu, int proto_version, const char *fmt, ...)
{
va_list ap;
int ret;
va_start(ap, fmt);
ret = p9pdu_vwritef(pdu, proto_version, fmt, ap);
va_end(ap);
return ret;
}
int p9stat_read(struct p9_client *clnt, char *buf, int len, struct p9_wstat *st)
{
struct p9_fcall fake_pdu;
int ret;
fake_pdu.size = len;
fake_pdu.capacity = len;
fake_pdu.sdata = buf;
fake_pdu.offset = 0;
ret = p9pdu_readf(&fake_pdu, clnt->proto_version, "S", st);
if (ret) {
p9_debug(P9_DEBUG_9P, "<<< p9stat_read failed: %d\n", ret);
trace_9p_protocol_dump(clnt, &fake_pdu);
}
return ret;
}
EXPORT_SYMBOL(p9stat_read);
int p9pdu_prepare(struct p9_fcall *pdu, int16_t tag, int8_t type)
{
pdu->id = type;
return p9pdu_writef(pdu, 0, "dbw", 0, type, tag);
}
int p9pdu_finalize(struct p9_client *clnt, struct p9_fcall *pdu)
{
int size = pdu->size;
int err;
pdu->size = 0;
err = p9pdu_writef(pdu, 0, "d", size);
pdu->size = size;
trace_9p_protocol_dump(clnt, pdu);
p9_debug(P9_DEBUG_9P, ">>> size=%d type: %d tag: %d\n",
pdu->size, pdu->id, pdu->tag);
return err;
}
void p9pdu_reset(struct p9_fcall *pdu)
{
pdu->offset = 0;
pdu->size = 0;
}
int p9dirent_read(struct p9_client *clnt, char *buf, int len,
struct p9_dirent *dirent)
{
struct p9_fcall fake_pdu;
int ret;
char *nameptr;
fake_pdu.size = len;
fake_pdu.capacity = len;
fake_pdu.sdata = buf;
fake_pdu.offset = 0;
ret = p9pdu_readf(&fake_pdu, clnt->proto_version, "Qqbs", &dirent->qid,
&dirent->d_off, &dirent->d_type, &nameptr);
if (ret) {
p9_debug(P9_DEBUG_9P, "<<< p9dirent_read failed: %d\n", ret);
trace_9p_protocol_dump(clnt, &fake_pdu);
goto out;
}
strcpy(dirent->d_name, nameptr);
kfree(nameptr);
out:
return fake_pdu.offset;
}
EXPORT_SYMBOL(p9dirent_read);

View File

@@ -0,0 +1,34 @@
/*
* net/9p/protocol.h
*
* 9P Protocol Support Code
*
* Copyright (C) 2008 by Eric Van Hensbergen <ericvh@gmail.com>
*
* Base on code from Anthony Liguori <aliguori@us.ibm.com>
* Copyright (C) 2008 by IBM, Corp.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
int p9pdu_vwritef(struct p9_fcall *pdu, int proto_version, const char *fmt,
va_list ap);
int p9pdu_readf(struct p9_fcall *pdu, int proto_version, const char *fmt, ...);
int p9pdu_prepare(struct p9_fcall *pdu, int16_t tag, int8_t type);
int p9pdu_finalize(struct p9_client *clnt, struct p9_fcall *pdu);
void p9pdu_reset(struct p9_fcall *pdu);
size_t pdu_read(struct p9_fcall *pdu, void *data, size_t size);

View File

@@ -0,0 +1,69 @@
/*
* Copyright IBM Corporation, 2010
* Author Venkateswararao Jujjuri <jvrao@linux.vnet.ibm.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2.1 of the GNU Lesser General Public License
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it would be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
*/
#include <linux/slab.h>
#include <linux/module.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
#include <linux/scatterlist.h>
#include "trans_common.h"
/**
* p9_release_req_pages - Release pages after the transaction.
*/
void p9_release_pages(struct page **pages, int nr_pages)
{
int i;
for (i = 0; i < nr_pages; i++)
if (pages[i])
put_page(pages[i]);
}
EXPORT_SYMBOL(p9_release_pages);
/**
* p9_nr_pages - Return number of pages needed to accommodate the payload.
*/
int p9_nr_pages(char *data, int len)
{
unsigned long start_page, end_page;
start_page = (unsigned long)data >> PAGE_SHIFT;
end_page = ((unsigned long)data + len + PAGE_SIZE - 1) >> PAGE_SHIFT;
return end_page - start_page;
}
EXPORT_SYMBOL(p9_nr_pages);
/**
* payload_gup - Translates user buffer into kernel pages and
* pins them either for read/write through get_user_pages_fast().
* @req: Request to be sent to server.
* @pdata_off: data offset into the first page after translation (gup).
* @pdata_len: Total length of the IO. gup may not return requested # of pages.
* @nr_pages: number of pages to accommodate the payload
* @rw: Indicates if the pages are for read or write.
*/
int p9_payload_gup(char *data, int *nr_pages, struct page **pages, int write)
{
int nr_mapped_pages;
nr_mapped_pages = get_user_pages_fast((unsigned long)data,
*nr_pages, write, pages);
if (nr_mapped_pages <= 0)
return nr_mapped_pages;
*nr_pages = nr_mapped_pages;
return 0;
}
EXPORT_SYMBOL(p9_payload_gup);

View File

@@ -0,0 +1,17 @@
/*
* Copyright IBM Corporation, 2010
* Author Venkateswararao Jujjuri <jvrao@linux.vnet.ibm.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2.1 of the GNU Lesser General Public License
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it would be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
*/
void p9_release_pages(struct page **, int);
int p9_payload_gup(char *, int *, struct page **, int);
int p9_nr_pages(char *, int);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,719 @@
/*
* linux/fs/9p/trans_rdma.c
*
* RDMA transport layer based on the trans_fd.c implementation.
*
* Copyright (C) 2008 by Tom Tucker <tom@opengridcomputing.com>
* Copyright (C) 2006 by Russ Cox <rsc@swtch.com>
* Copyright (C) 2004-2005 by Latchesar Ionkov <lucho@ionkov.net>
* Copyright (C) 2004-2008 by Eric Van Hensbergen <ericvh@gmail.com>
* Copyright (C) 1997-2002 by Ron Minnich <rminnich@sarnoff.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/in.h>
#include <linux/module.h>
#include <linux/net.h>
#include <linux/ipv6.h>
#include <linux/kthread.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/un.h>
#include <linux/uaccess.h>
#include <linux/inet.h>
#include <linux/idr.h>
#include <linux/file.h>
#include <linux/parser.h>
#include <linux/semaphore.h>
#include <linux/slab.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
#include <net/9p/transport.h>
#include <rdma/ib_verbs.h>
#include <rdma/rdma_cm.h>
#define P9_PORT 5640
#define P9_RDMA_SQ_DEPTH 32
#define P9_RDMA_RQ_DEPTH 32
#define P9_RDMA_SEND_SGE 4
#define P9_RDMA_RECV_SGE 4
#define P9_RDMA_IRD 0
#define P9_RDMA_ORD 0
#define P9_RDMA_TIMEOUT 30000 /* 30 seconds */
#define P9_RDMA_MAXSIZE (4*4096) /* Min SGE is 4, so we can
* safely advertise a maxsize
* of 64k */
/**
* struct p9_trans_rdma - RDMA transport instance
*
* @state: tracks the transport state machine for connection setup and tear down
* @cm_id: The RDMA CM ID
* @pd: Protection Domain pointer
* @qp: Queue Pair pointer
* @cq: Completion Queue pointer
* @dm_mr: DMA Memory Region pointer
* @lkey: The local access only memory region key
* @timeout: Number of uSecs to wait for connection management events
* @sq_depth: The depth of the Send Queue
* @sq_sem: Semaphore for the SQ
* @rq_depth: The depth of the Receive Queue.
* @rq_count: Count of requests in the Receive Queue.
* @addr: The remote peer's address
* @req_lock: Protects the active request list
* @cm_done: Completion event for connection management tracking
*/
struct p9_trans_rdma {
enum {
P9_RDMA_INIT,
P9_RDMA_ADDR_RESOLVED,
P9_RDMA_ROUTE_RESOLVED,
P9_RDMA_CONNECTED,
P9_RDMA_FLUSHING,
P9_RDMA_CLOSING,
P9_RDMA_CLOSED,
} state;
struct rdma_cm_id *cm_id;
struct ib_pd *pd;
struct ib_qp *qp;
struct ib_cq *cq;
struct ib_mr *dma_mr;
u32 lkey;
long timeout;
int sq_depth;
struct semaphore sq_sem;
int rq_depth;
atomic_t rq_count;
struct sockaddr_in addr;
spinlock_t req_lock;
struct completion cm_done;
};
/**
* p9_rdma_context - Keeps track of in-process WR
*
* @wc_op: The original WR op for when the CQE completes in error.
* @busa: Bus address to unmap when the WR completes
* @req: Keeps track of requests (send)
* @rc: Keepts track of replies (receive)
*/
struct p9_rdma_req;
struct p9_rdma_context {
enum ib_wc_opcode wc_op;
dma_addr_t busa;
union {
struct p9_req_t *req;
struct p9_fcall *rc;
};
};
/**
* p9_rdma_opts - Collection of mount options
* @port: port of connection
* @sq_depth: The requested depth of the SQ. This really doesn't need
* to be any deeper than the number of threads used in the client
* @rq_depth: The depth of the RQ. Should be greater than or equal to SQ depth
* @timeout: Time to wait in msecs for CM events
*/
struct p9_rdma_opts {
short port;
int sq_depth;
int rq_depth;
long timeout;
};
/*
* Option Parsing (code inspired by NFS code)
*/
enum {
/* Options that take integer arguments */
Opt_port, Opt_rq_depth, Opt_sq_depth, Opt_timeout, Opt_err,
};
static match_table_t tokens = {
{Opt_port, "port=%u"},
{Opt_sq_depth, "sq=%u"},
{Opt_rq_depth, "rq=%u"},
{Opt_timeout, "timeout=%u"},
{Opt_err, NULL},
};
/**
* parse_opts - parse mount options into rdma options structure
* @params: options string passed from mount
* @opts: rdma transport-specific structure to parse options into
*
* Returns 0 upon success, -ERRNO upon failure
*/
static int parse_opts(char *params, struct p9_rdma_opts *opts)
{
char *p;
substring_t args[MAX_OPT_ARGS];
int option;
char *options, *tmp_options;
opts->port = P9_PORT;
opts->sq_depth = P9_RDMA_SQ_DEPTH;
opts->rq_depth = P9_RDMA_RQ_DEPTH;
opts->timeout = P9_RDMA_TIMEOUT;
if (!params)
return 0;
tmp_options = kstrdup(params, GFP_KERNEL);
if (!tmp_options) {
p9_debug(P9_DEBUG_ERROR,
"failed to allocate copy of option string\n");
return -ENOMEM;
}
options = tmp_options;
while ((p = strsep(&options, ",")) != NULL) {
int token;
int r;
if (!*p)
continue;
token = match_token(p, tokens, args);
r = match_int(&args[0], &option);
if (r < 0) {
p9_debug(P9_DEBUG_ERROR,
"integer field, but no integer?\n");
continue;
}
switch (token) {
case Opt_port:
opts->port = option;
break;
case Opt_sq_depth:
opts->sq_depth = option;
break;
case Opt_rq_depth:
opts->rq_depth = option;
break;
case Opt_timeout:
opts->timeout = option;
break;
default:
continue;
}
}
/* RQ must be at least as large as the SQ */
opts->rq_depth = max(opts->rq_depth, opts->sq_depth);
kfree(tmp_options);
return 0;
}
static int
p9_cm_event_handler(struct rdma_cm_id *id, struct rdma_cm_event *event)
{
struct p9_client *c = id->context;
struct p9_trans_rdma *rdma = c->trans;
switch (event->event) {
case RDMA_CM_EVENT_ADDR_RESOLVED:
BUG_ON(rdma->state != P9_RDMA_INIT);
rdma->state = P9_RDMA_ADDR_RESOLVED;
break;
case RDMA_CM_EVENT_ROUTE_RESOLVED:
BUG_ON(rdma->state != P9_RDMA_ADDR_RESOLVED);
rdma->state = P9_RDMA_ROUTE_RESOLVED;
break;
case RDMA_CM_EVENT_ESTABLISHED:
BUG_ON(rdma->state != P9_RDMA_ROUTE_RESOLVED);
rdma->state = P9_RDMA_CONNECTED;
break;
case RDMA_CM_EVENT_DISCONNECTED:
if (rdma)
rdma->state = P9_RDMA_CLOSED;
if (c)
c->status = Disconnected;
break;
case RDMA_CM_EVENT_TIMEWAIT_EXIT:
break;
case RDMA_CM_EVENT_ADDR_CHANGE:
case RDMA_CM_EVENT_ROUTE_ERROR:
case RDMA_CM_EVENT_DEVICE_REMOVAL:
case RDMA_CM_EVENT_MULTICAST_JOIN:
case RDMA_CM_EVENT_MULTICAST_ERROR:
case RDMA_CM_EVENT_REJECTED:
case RDMA_CM_EVENT_CONNECT_REQUEST:
case RDMA_CM_EVENT_CONNECT_RESPONSE:
case RDMA_CM_EVENT_CONNECT_ERROR:
case RDMA_CM_EVENT_ADDR_ERROR:
case RDMA_CM_EVENT_UNREACHABLE:
c->status = Disconnected;
rdma_disconnect(rdma->cm_id);
break;
default:
BUG();
}
complete(&rdma->cm_done);
return 0;
}
static void
handle_recv(struct p9_client *client, struct p9_trans_rdma *rdma,
struct p9_rdma_context *c, enum ib_wc_status status, u32 byte_len)
{
struct p9_req_t *req;
int err = 0;
int16_t tag;
req = NULL;
ib_dma_unmap_single(rdma->cm_id->device, c->busa, client->msize,
DMA_FROM_DEVICE);
if (status != IB_WC_SUCCESS)
goto err_out;
err = p9_parse_header(c->rc, NULL, NULL, &tag, 1);
if (err)
goto err_out;
req = p9_tag_lookup(client, tag);
if (!req)
goto err_out;
req->rc = c->rc;
req->status = REQ_STATUS_RCVD;
p9_client_cb(client, req);
return;
err_out:
p9_debug(P9_DEBUG_ERROR, "req %p err %d status %d\n", req, err, status);
rdma->state = P9_RDMA_FLUSHING;
client->status = Disconnected;
}
static void
handle_send(struct p9_client *client, struct p9_trans_rdma *rdma,
struct p9_rdma_context *c, enum ib_wc_status status, u32 byte_len)
{
ib_dma_unmap_single(rdma->cm_id->device,
c->busa, c->req->tc->size,
DMA_TO_DEVICE);
}
static void qp_event_handler(struct ib_event *event, void *context)
{
p9_debug(P9_DEBUG_ERROR, "QP event %d context %p\n",
event->event, context);
}
static void cq_comp_handler(struct ib_cq *cq, void *cq_context)
{
struct p9_client *client = cq_context;
struct p9_trans_rdma *rdma = client->trans;
int ret;
struct ib_wc wc;
ib_req_notify_cq(rdma->cq, IB_CQ_NEXT_COMP);
while ((ret = ib_poll_cq(cq, 1, &wc)) > 0) {
struct p9_rdma_context *c = (void *) (unsigned long) wc.wr_id;
switch (c->wc_op) {
case IB_WC_RECV:
atomic_dec(&rdma->rq_count);
handle_recv(client, rdma, c, wc.status, wc.byte_len);
break;
case IB_WC_SEND:
handle_send(client, rdma, c, wc.status, wc.byte_len);
up(&rdma->sq_sem);
break;
default:
pr_err("unexpected completion type, c->wc_op=%d, wc.opcode=%d, status=%d\n",
c->wc_op, wc.opcode, wc.status);
break;
}
kfree(c);
}
}
static void cq_event_handler(struct ib_event *e, void *v)
{
p9_debug(P9_DEBUG_ERROR, "CQ event %d context %p\n", e->event, v);
}
static void rdma_destroy_trans(struct p9_trans_rdma *rdma)
{
if (!rdma)
return;
if (rdma->dma_mr && !IS_ERR(rdma->dma_mr))
ib_dereg_mr(rdma->dma_mr);
if (rdma->qp && !IS_ERR(rdma->qp))
ib_destroy_qp(rdma->qp);
if (rdma->pd && !IS_ERR(rdma->pd))
ib_dealloc_pd(rdma->pd);
if (rdma->cq && !IS_ERR(rdma->cq))
ib_destroy_cq(rdma->cq);
if (rdma->cm_id && !IS_ERR(rdma->cm_id))
rdma_destroy_id(rdma->cm_id);
kfree(rdma);
}
static int
post_recv(struct p9_client *client, struct p9_rdma_context *c)
{
struct p9_trans_rdma *rdma = client->trans;
struct ib_recv_wr wr, *bad_wr;
struct ib_sge sge;
c->busa = ib_dma_map_single(rdma->cm_id->device,
c->rc->sdata, client->msize,
DMA_FROM_DEVICE);
if (ib_dma_mapping_error(rdma->cm_id->device, c->busa))
goto error;
sge.addr = c->busa;
sge.length = client->msize;
sge.lkey = rdma->lkey;
wr.next = NULL;
c->wc_op = IB_WC_RECV;
wr.wr_id = (unsigned long) c;
wr.sg_list = &sge;
wr.num_sge = 1;
return ib_post_recv(rdma->qp, &wr, &bad_wr);
error:
p9_debug(P9_DEBUG_ERROR, "EIO\n");
return -EIO;
}
static int rdma_request(struct p9_client *client, struct p9_req_t *req)
{
struct p9_trans_rdma *rdma = client->trans;
struct ib_send_wr wr, *bad_wr;
struct ib_sge sge;
int err = 0;
unsigned long flags;
struct p9_rdma_context *c = NULL;
struct p9_rdma_context *rpl_context = NULL;
/* Allocate an fcall for the reply */
rpl_context = kmalloc(sizeof *rpl_context, GFP_NOFS);
if (!rpl_context) {
err = -ENOMEM;
goto err_close;
}
/*
* If the request has a buffer, steal it, otherwise
* allocate a new one. Typically, requests should already
* have receive buffers allocated and just swap them around
*/
if (!req->rc) {
req->rc = kmalloc(sizeof(struct p9_fcall)+client->msize,
GFP_NOFS);
if (req->rc) {
req->rc->sdata = (char *) req->rc +
sizeof(struct p9_fcall);
req->rc->capacity = client->msize;
}
}
rpl_context->rc = req->rc;
if (!rpl_context->rc) {
err = -ENOMEM;
goto err_free2;
}
/*
* Post a receive buffer for this request. We need to ensure
* there is a reply buffer available for every outstanding
* request. A flushed request can result in no reply for an
* outstanding request, so we must keep a count to avoid
* overflowing the RQ.
*/
if (atomic_inc_return(&rdma->rq_count) <= rdma->rq_depth) {
err = post_recv(client, rpl_context);
if (err)
goto err_free1;
} else
atomic_dec(&rdma->rq_count);
/* remove posted receive buffer from request structure */
req->rc = NULL;
/* Post the request */
c = kmalloc(sizeof *c, GFP_NOFS);
if (!c) {
err = -ENOMEM;
goto err_free1;
}
c->req = req;
c->busa = ib_dma_map_single(rdma->cm_id->device,
c->req->tc->sdata, c->req->tc->size,
DMA_TO_DEVICE);
if (ib_dma_mapping_error(rdma->cm_id->device, c->busa))
goto error;
sge.addr = c->busa;
sge.length = c->req->tc->size;
sge.lkey = rdma->lkey;
wr.next = NULL;
c->wc_op = IB_WC_SEND;
wr.wr_id = (unsigned long) c;
wr.opcode = IB_WR_SEND;
wr.send_flags = IB_SEND_SIGNALED;
wr.sg_list = &sge;
wr.num_sge = 1;
if (down_interruptible(&rdma->sq_sem))
goto error;
return ib_post_send(rdma->qp, &wr, &bad_wr);
error:
kfree(c);
kfree(rpl_context->rc);
kfree(rpl_context);
p9_debug(P9_DEBUG_ERROR, "EIO\n");
return -EIO;
err_free1:
kfree(rpl_context->rc);
err_free2:
kfree(rpl_context);
err_close:
spin_lock_irqsave(&rdma->req_lock, flags);
if (rdma->state < P9_RDMA_CLOSING) {
rdma->state = P9_RDMA_CLOSING;
spin_unlock_irqrestore(&rdma->req_lock, flags);
rdma_disconnect(rdma->cm_id);
} else
spin_unlock_irqrestore(&rdma->req_lock, flags);
return err;
}
static void rdma_close(struct p9_client *client)
{
struct p9_trans_rdma *rdma;
if (!client)
return;
rdma = client->trans;
if (!rdma)
return;
client->status = Disconnected;
rdma_disconnect(rdma->cm_id);
rdma_destroy_trans(rdma);
}
/**
* alloc_rdma - Allocate and initialize the rdma transport structure
* @opts: Mount options structure
*/
static struct p9_trans_rdma *alloc_rdma(struct p9_rdma_opts *opts)
{
struct p9_trans_rdma *rdma;
rdma = kzalloc(sizeof(struct p9_trans_rdma), GFP_KERNEL);
if (!rdma)
return NULL;
rdma->sq_depth = opts->sq_depth;
rdma->rq_depth = opts->rq_depth;
rdma->timeout = opts->timeout;
spin_lock_init(&rdma->req_lock);
init_completion(&rdma->cm_done);
sema_init(&rdma->sq_sem, rdma->sq_depth);
atomic_set(&rdma->rq_count, 0);
return rdma;
}
/* its not clear to me we can do anything after send has been posted */
static int rdma_cancel(struct p9_client *client, struct p9_req_t *req)
{
return 1;
}
/**
* trans_create_rdma - Transport method for creating atransport instance
* @client: client instance
* @addr: IP address string
* @args: Mount options string
*/
static int
rdma_create_trans(struct p9_client *client, const char *addr, char *args)
{
int err;
struct p9_rdma_opts opts;
struct p9_trans_rdma *rdma;
struct rdma_conn_param conn_param;
struct ib_qp_init_attr qp_attr;
struct ib_device_attr devattr;
/* Parse the transport specific mount options */
err = parse_opts(args, &opts);
if (err < 0)
return err;
/* Create and initialize the RDMA transport structure */
rdma = alloc_rdma(&opts);
if (!rdma)
return -ENOMEM;
/* Create the RDMA CM ID */
rdma->cm_id = rdma_create_id(p9_cm_event_handler, client, RDMA_PS_TCP,
IB_QPT_RC);
if (IS_ERR(rdma->cm_id))
goto error;
/* Associate the client with the transport */
client->trans = rdma;
/* Resolve the server's address */
rdma->addr.sin_family = AF_INET;
rdma->addr.sin_addr.s_addr = in_aton(addr);
rdma->addr.sin_port = htons(opts.port);
err = rdma_resolve_addr(rdma->cm_id, NULL,
(struct sockaddr *)&rdma->addr,
rdma->timeout);
if (err)
goto error;
err = wait_for_completion_interruptible(&rdma->cm_done);
if (err || (rdma->state != P9_RDMA_ADDR_RESOLVED))
goto error;
/* Resolve the route to the server */
err = rdma_resolve_route(rdma->cm_id, rdma->timeout);
if (err)
goto error;
err = wait_for_completion_interruptible(&rdma->cm_done);
if (err || (rdma->state != P9_RDMA_ROUTE_RESOLVED))
goto error;
/* Query the device attributes */
err = ib_query_device(rdma->cm_id->device, &devattr);
if (err)
goto error;
/* Create the Completion Queue */
rdma->cq = ib_create_cq(rdma->cm_id->device, cq_comp_handler,
cq_event_handler, client,
opts.sq_depth + opts.rq_depth + 1, 0);
if (IS_ERR(rdma->cq))
goto error;
ib_req_notify_cq(rdma->cq, IB_CQ_NEXT_COMP);
/* Create the Protection Domain */
rdma->pd = ib_alloc_pd(rdma->cm_id->device);
if (IS_ERR(rdma->pd))
goto error;
/* Cache the DMA lkey in the transport */
rdma->dma_mr = NULL;
if (devattr.device_cap_flags & IB_DEVICE_LOCAL_DMA_LKEY)
rdma->lkey = rdma->cm_id->device->local_dma_lkey;
else {
rdma->dma_mr = ib_get_dma_mr(rdma->pd, IB_ACCESS_LOCAL_WRITE);
if (IS_ERR(rdma->dma_mr))
goto error;
rdma->lkey = rdma->dma_mr->lkey;
}
/* Create the Queue Pair */
memset(&qp_attr, 0, sizeof qp_attr);
qp_attr.event_handler = qp_event_handler;
qp_attr.qp_context = client;
qp_attr.cap.max_send_wr = opts.sq_depth;
qp_attr.cap.max_recv_wr = opts.rq_depth;
qp_attr.cap.max_send_sge = P9_RDMA_SEND_SGE;
qp_attr.cap.max_recv_sge = P9_RDMA_RECV_SGE;
qp_attr.sq_sig_type = IB_SIGNAL_REQ_WR;
qp_attr.qp_type = IB_QPT_RC;
qp_attr.send_cq = rdma->cq;
qp_attr.recv_cq = rdma->cq;
err = rdma_create_qp(rdma->cm_id, rdma->pd, &qp_attr);
if (err)
goto error;
rdma->qp = rdma->cm_id->qp;
/* Request a connection */
memset(&conn_param, 0, sizeof(conn_param));
conn_param.private_data = NULL;
conn_param.private_data_len = 0;
conn_param.responder_resources = P9_RDMA_IRD;
conn_param.initiator_depth = P9_RDMA_ORD;
err = rdma_connect(rdma->cm_id, &conn_param);
if (err)
goto error;
err = wait_for_completion_interruptible(&rdma->cm_done);
if (err || (rdma->state != P9_RDMA_CONNECTED))
goto error;
client->status = Connected;
return 0;
error:
rdma_destroy_trans(rdma);
return -ENOTCONN;
}
static struct p9_trans_module p9_rdma_trans = {
.name = "rdma",
.maxsize = P9_RDMA_MAXSIZE,
.def = 0,
.owner = THIS_MODULE,
.create = rdma_create_trans,
.close = rdma_close,
.request = rdma_request,
.cancel = rdma_cancel,
};
/**
* p9_trans_rdma_init - Register the 9P RDMA transport driver
*/
static int __init p9_trans_rdma_init(void)
{
v9fs_register_trans(&p9_rdma_trans);
return 0;
}
static void __exit p9_trans_rdma_exit(void)
{
v9fs_unregister_trans(&p9_rdma_trans);
}
module_init(p9_trans_rdma_init);
module_exit(p9_trans_rdma_exit);
MODULE_AUTHOR("Tom Tucker <tom@opengridcomputing.com>");
MODULE_DESCRIPTION("RDMA Transport for 9P");
MODULE_LICENSE("Dual BSD/GPL");

View File

@@ -0,0 +1,730 @@
/*
* The Virtio 9p transport driver
*
* This is a block based transport driver based on the lguest block driver
* code.
*
* Copyright (C) 2007, 2008 Eric Van Hensbergen, IBM Corporation
*
* Based on virtio console driver
* Copyright (C) 2006, 2007 Rusty Russell, IBM Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/in.h>
#include <linux/module.h>
#include <linux/net.h>
#include <linux/ipv6.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/un.h>
#include <linux/uaccess.h>
#include <linux/inet.h>
#include <linux/idr.h>
#include <linux/file.h>
#include <linux/highmem.h>
#include <linux/slab.h>
#include <net/9p/9p.h>
#include <linux/parser.h>
#include <net/9p/client.h>
#include <net/9p/transport.h>
#include <linux/scatterlist.h>
#include <linux/swap.h>
#include <linux/virtio.h>
#include <linux/virtio_9p.h>
#include "trans_common.h"
#define VIRTQUEUE_NUM 128
/* a single mutex to manage channel initialization and attachment */
static DEFINE_MUTEX(virtio_9p_lock);
static DECLARE_WAIT_QUEUE_HEAD(vp_wq);
static atomic_t vp_pinned = ATOMIC_INIT(0);
/**
* struct virtio_chan - per-instance transport information
* @initialized: whether the channel is initialized
* @inuse: whether the channel is in use
* @lock: protects multiple elements within this structure
* @client: client instance
* @vdev: virtio dev associated with this channel
* @vq: virtio queue associated with this channel
* @sg: scatter gather list which is used to pack a request (protected?)
*
* We keep all per-channel information in a structure.
* This structure is allocated within the devices dev->mem space.
* A pointer to the structure will get put in the transport private.
*
*/
struct virtio_chan {
bool inuse;
spinlock_t lock;
struct p9_client *client;
struct virtio_device *vdev;
struct virtqueue *vq;
int ring_bufs_avail;
wait_queue_head_t *vc_wq;
/* This is global limit. Since we don't have a global structure,
* will be placing it in each channel.
*/
unsigned long p9_max_pages;
/* Scatterlist: can be too big for stack. */
struct scatterlist sg[VIRTQUEUE_NUM];
int tag_len;
/*
* tag name to identify a mount Non-null terminated
*/
char *tag;
struct list_head chan_list;
};
static struct list_head virtio_chan_list;
/* How many bytes left in this page. */
static unsigned int rest_of_page(void *data)
{
return PAGE_SIZE - ((unsigned long)data % PAGE_SIZE);
}
/**
* p9_virtio_close - reclaim resources of a channel
* @client: client instance
*
* This reclaims a channel by freeing its resources and
* reseting its inuse flag.
*
*/
static void p9_virtio_close(struct p9_client *client)
{
struct virtio_chan *chan = client->trans;
mutex_lock(&virtio_9p_lock);
if (chan)
chan->inuse = false;
mutex_unlock(&virtio_9p_lock);
}
/**
* req_done - callback which signals activity from the server
* @vq: virtio queue activity was received on
*
* This notifies us that the server has triggered some activity
* on the virtio channel - most likely a response to request we
* sent. Figure out which requests now have responses and wake up
* those threads.
*
* Bugs: could do with some additional sanity checking, but appears to work.
*
*/
static void req_done(struct virtqueue *vq)
{
struct virtio_chan *chan = vq->vdev->priv;
struct p9_fcall *rc;
unsigned int len;
struct p9_req_t *req;
unsigned long flags;
p9_debug(P9_DEBUG_TRANS, ": request done\n");
while (1) {
spin_lock_irqsave(&chan->lock, flags);
rc = virtqueue_get_buf(chan->vq, &len);
if (rc == NULL) {
spin_unlock_irqrestore(&chan->lock, flags);
break;
}
chan->ring_bufs_avail = 1;
spin_unlock_irqrestore(&chan->lock, flags);
/* Wakeup if anyone waiting for VirtIO ring space. */
wake_up(chan->vc_wq);
p9_debug(P9_DEBUG_TRANS, ": rc %p\n", rc);
p9_debug(P9_DEBUG_TRANS, ": lookup tag %d\n", rc->tag);
req = p9_tag_lookup(chan->client, rc->tag);
req->status = REQ_STATUS_RCVD;
p9_client_cb(chan->client, req);
}
}
/**
* pack_sg_list - pack a scatter gather list from a linear buffer
* @sg: scatter/gather list to pack into
* @start: which segment of the sg_list to start at
* @limit: maximum segment to pack data to
* @data: data to pack into scatter/gather list
* @count: amount of data to pack into the scatter/gather list
*
* sg_lists have multiple segments of various sizes. This will pack
* arbitrary data into an existing scatter gather list, segmenting the
* data as necessary within constraints.
*
*/
static int pack_sg_list(struct scatterlist *sg, int start,
int limit, char *data, int count)
{
int s;
int index = start;
while (count) {
s = rest_of_page(data);
if (s > count)
s = count;
BUG_ON(index > limit);
/* Make sure we don't terminate early. */
sg_unmark_end(&sg[index]);
sg_set_buf(&sg[index++], data, s);
count -= s;
data += s;
}
if (index-start)
sg_mark_end(&sg[index - 1]);
return index-start;
}
/* We don't currently allow canceling of virtio requests */
static int p9_virtio_cancel(struct p9_client *client, struct p9_req_t *req)
{
return 1;
}
/**
* pack_sg_list_p - Just like pack_sg_list. Instead of taking a buffer,
* this takes a list of pages.
* @sg: scatter/gather list to pack into
* @start: which segment of the sg_list to start at
* @pdata: a list of pages to add into sg.
* @nr_pages: number of pages to pack into the scatter/gather list
* @data: data to pack into scatter/gather list
* @count: amount of data to pack into the scatter/gather list
*/
static int
pack_sg_list_p(struct scatterlist *sg, int start, int limit,
struct page **pdata, int nr_pages, char *data, int count)
{
int i = 0, s;
int data_off;
int index = start;
BUG_ON(nr_pages > (limit - start));
/*
* if the first page doesn't start at
* page boundary find the offset
*/
data_off = offset_in_page(data);
while (nr_pages) {
s = rest_of_page(data);
if (s > count)
s = count;
/* Make sure we don't terminate early. */
sg_unmark_end(&sg[index]);
sg_set_page(&sg[index++], pdata[i++], s, data_off);
data_off = 0;
data += s;
count -= s;
nr_pages--;
}
if (index-start)
sg_mark_end(&sg[index - 1]);
return index - start;
}
/**
* p9_virtio_request - issue a request
* @client: client instance issuing the request
* @req: request to be issued
*
*/
static int
p9_virtio_request(struct p9_client *client, struct p9_req_t *req)
{
int err;
int in, out, out_sgs, in_sgs;
unsigned long flags;
struct virtio_chan *chan = client->trans;
struct scatterlist *sgs[2];
p9_debug(P9_DEBUG_TRANS, "9p debug: virtio request\n");
req->status = REQ_STATUS_SENT;
req_retry:
spin_lock_irqsave(&chan->lock, flags);
out_sgs = in_sgs = 0;
/* Handle out VirtIO ring buffers */
out = pack_sg_list(chan->sg, 0,
VIRTQUEUE_NUM, req->tc->sdata, req->tc->size);
if (out)
sgs[out_sgs++] = chan->sg;
in = pack_sg_list(chan->sg, out,
VIRTQUEUE_NUM, req->rc->sdata, req->rc->capacity);
if (in)
sgs[out_sgs + in_sgs++] = chan->sg + out;
err = virtqueue_add_sgs(chan->vq, sgs, out_sgs, in_sgs, req->tc,
GFP_ATOMIC);
if (err < 0) {
if (err == -ENOSPC) {
chan->ring_bufs_avail = 0;
spin_unlock_irqrestore(&chan->lock, flags);
err = wait_event_interruptible(*chan->vc_wq,
chan->ring_bufs_avail);
if (err == -ERESTARTSYS)
return err;
p9_debug(P9_DEBUG_TRANS, "Retry virtio request\n");
goto req_retry;
} else {
spin_unlock_irqrestore(&chan->lock, flags);
p9_debug(P9_DEBUG_TRANS,
"virtio rpc add_sgs returned failure\n");
return -EIO;
}
}
virtqueue_kick(chan->vq);
spin_unlock_irqrestore(&chan->lock, flags);
p9_debug(P9_DEBUG_TRANS, "virtio request kicked\n");
return 0;
}
static int p9_get_mapped_pages(struct virtio_chan *chan,
struct page **pages, char *data,
int nr_pages, int write, int kern_buf)
{
int err;
if (!kern_buf) {
/*
* We allow only p9_max_pages pinned. We wait for the
* Other zc request to finish here
*/
if (atomic_read(&vp_pinned) >= chan->p9_max_pages) {
err = wait_event_interruptible(vp_wq,
(atomic_read(&vp_pinned) < chan->p9_max_pages));
if (err == -ERESTARTSYS)
return err;
}
err = p9_payload_gup(data, &nr_pages, pages, write);
if (err < 0)
return err;
atomic_add(nr_pages, &vp_pinned);
} else {
/* kernel buffer, no need to pin pages */
int s, index = 0;
int count = nr_pages;
while (nr_pages) {
s = rest_of_page(data);
if (is_vmalloc_addr(data))
pages[index++] = vmalloc_to_page(data);
else
pages[index++] = kmap_to_page(data);
data += s;
nr_pages--;
}
nr_pages = count;
}
return nr_pages;
}
/**
* p9_virtio_zc_request - issue a zero copy request
* @client: client instance issuing the request
* @req: request to be issued
* @uidata: user bffer that should be ued for zero copy read
* @uodata: user buffer that shoud be user for zero copy write
* @inlen: read buffer size
* @olen: write buffer size
* @hdrlen: reader header size, This is the size of response protocol data
*
*/
static int
p9_virtio_zc_request(struct p9_client *client, struct p9_req_t *req,
char *uidata, char *uodata, int inlen,
int outlen, int in_hdr_len, int kern_buf)
{
int in, out, err, out_sgs, in_sgs;
unsigned long flags;
int in_nr_pages = 0, out_nr_pages = 0;
struct page **in_pages = NULL, **out_pages = NULL;
struct virtio_chan *chan = client->trans;
struct scatterlist *sgs[4];
p9_debug(P9_DEBUG_TRANS, "virtio request\n");
if (uodata) {
out_nr_pages = p9_nr_pages(uodata, outlen);
out_pages = kmalloc(sizeof(struct page *) * out_nr_pages,
GFP_NOFS);
if (!out_pages) {
err = -ENOMEM;
goto err_out;
}
out_nr_pages = p9_get_mapped_pages(chan, out_pages, uodata,
out_nr_pages, 0, kern_buf);
if (out_nr_pages < 0) {
err = out_nr_pages;
kfree(out_pages);
out_pages = NULL;
goto err_out;
}
}
if (uidata) {
in_nr_pages = p9_nr_pages(uidata, inlen);
in_pages = kmalloc(sizeof(struct page *) * in_nr_pages,
GFP_NOFS);
if (!in_pages) {
err = -ENOMEM;
goto err_out;
}
in_nr_pages = p9_get_mapped_pages(chan, in_pages, uidata,
in_nr_pages, 1, kern_buf);
if (in_nr_pages < 0) {
err = in_nr_pages;
kfree(in_pages);
in_pages = NULL;
goto err_out;
}
}
req->status = REQ_STATUS_SENT;
req_retry_pinned:
spin_lock_irqsave(&chan->lock, flags);
out_sgs = in_sgs = 0;
/* out data */
out = pack_sg_list(chan->sg, 0,
VIRTQUEUE_NUM, req->tc->sdata, req->tc->size);
if (out)
sgs[out_sgs++] = chan->sg;
if (out_pages) {
sgs[out_sgs++] = chan->sg + out;
out += pack_sg_list_p(chan->sg, out, VIRTQUEUE_NUM,
out_pages, out_nr_pages, uodata, outlen);
}
/*
* Take care of in data
* For example TREAD have 11.
* 11 is the read/write header = PDU Header(7) + IO Size (4).
* Arrange in such a way that server places header in the
* alloced memory and payload onto the user buffer.
*/
in = pack_sg_list(chan->sg, out,
VIRTQUEUE_NUM, req->rc->sdata, in_hdr_len);
if (in)
sgs[out_sgs + in_sgs++] = chan->sg + out;
if (in_pages) {
sgs[out_sgs + in_sgs++] = chan->sg + out + in;
in += pack_sg_list_p(chan->sg, out + in, VIRTQUEUE_NUM,
in_pages, in_nr_pages, uidata, inlen);
}
BUG_ON(out_sgs + in_sgs > ARRAY_SIZE(sgs));
err = virtqueue_add_sgs(chan->vq, sgs, out_sgs, in_sgs, req->tc,
GFP_ATOMIC);
if (err < 0) {
if (err == -ENOSPC) {
chan->ring_bufs_avail = 0;
spin_unlock_irqrestore(&chan->lock, flags);
err = wait_event_interruptible(*chan->vc_wq,
chan->ring_bufs_avail);
if (err == -ERESTARTSYS)
goto err_out;
p9_debug(P9_DEBUG_TRANS, "Retry virtio request\n");
goto req_retry_pinned;
} else {
spin_unlock_irqrestore(&chan->lock, flags);
p9_debug(P9_DEBUG_TRANS,
"virtio rpc add_sgs returned failure\n");
err = -EIO;
goto err_out;
}
}
virtqueue_kick(chan->vq);
spin_unlock_irqrestore(&chan->lock, flags);
p9_debug(P9_DEBUG_TRANS, "virtio request kicked\n");
err = wait_event_interruptible(*req->wq,
req->status >= REQ_STATUS_RCVD);
/*
* Non kernel buffers are pinned, unpin them
*/
err_out:
if (!kern_buf) {
if (in_pages) {
p9_release_pages(in_pages, in_nr_pages);
atomic_sub(in_nr_pages, &vp_pinned);
}
if (out_pages) {
p9_release_pages(out_pages, out_nr_pages);
atomic_sub(out_nr_pages, &vp_pinned);
}
/* wakeup anybody waiting for slots to pin pages */
wake_up(&vp_wq);
}
kfree(in_pages);
kfree(out_pages);
return err;
}
static ssize_t p9_mount_tag_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct virtio_chan *chan;
struct virtio_device *vdev;
vdev = dev_to_virtio(dev);
chan = vdev->priv;
return snprintf(buf, chan->tag_len + 1, "%s", chan->tag);
}
static DEVICE_ATTR(mount_tag, 0444, p9_mount_tag_show, NULL);
/**
* p9_virtio_probe - probe for existence of 9P virtio channels
* @vdev: virtio device to probe
*
* This probes for existing virtio channels.
*
*/
static int p9_virtio_probe(struct virtio_device *vdev)
{
__u16 tag_len;
char *tag;
int err;
struct virtio_chan *chan;
chan = kmalloc(sizeof(struct virtio_chan), GFP_KERNEL);
if (!chan) {
pr_err("Failed to allocate virtio 9P channel\n");
err = -ENOMEM;
goto fail;
}
chan->vdev = vdev;
/* We expect one virtqueue, for requests. */
chan->vq = virtio_find_single_vq(vdev, req_done, "requests");
if (IS_ERR(chan->vq)) {
err = PTR_ERR(chan->vq);
goto out_free_vq;
}
chan->vq->vdev->priv = chan;
spin_lock_init(&chan->lock);
sg_init_table(chan->sg, VIRTQUEUE_NUM);
chan->inuse = false;
if (virtio_has_feature(vdev, VIRTIO_9P_MOUNT_TAG)) {
vdev->config->get(vdev,
offsetof(struct virtio_9p_config, tag_len),
&tag_len, sizeof(tag_len));
} else {
err = -EINVAL;
goto out_free_vq;
}
tag = kmalloc(tag_len, GFP_KERNEL);
if (!tag) {
err = -ENOMEM;
goto out_free_vq;
}
vdev->config->get(vdev, offsetof(struct virtio_9p_config, tag),
tag, tag_len);
chan->tag = tag;
chan->tag_len = tag_len;
err = sysfs_create_file(&(vdev->dev.kobj), &dev_attr_mount_tag.attr);
if (err) {
goto out_free_tag;
}
chan->vc_wq = kmalloc(sizeof(wait_queue_head_t), GFP_KERNEL);
if (!chan->vc_wq) {
err = -ENOMEM;
goto out_free_tag;
}
init_waitqueue_head(chan->vc_wq);
chan->ring_bufs_avail = 1;
/* Ceiling limit to avoid denial of service attacks */
chan->p9_max_pages = nr_free_buffer_pages()/4;
mutex_lock(&virtio_9p_lock);
list_add_tail(&chan->chan_list, &virtio_chan_list);
mutex_unlock(&virtio_9p_lock);
/* Let udev rules use the new mount_tag attribute. */
kobject_uevent(&(vdev->dev.kobj), KOBJ_CHANGE);
return 0;
out_free_tag:
kfree(tag);
out_free_vq:
vdev->config->del_vqs(vdev);
kfree(chan);
fail:
return err;
}
/**
* p9_virtio_create - allocate a new virtio channel
* @client: client instance invoking this transport
* @devname: string identifying the channel to connect to (unused)
* @args: args passed from sys_mount() for per-transport options (unused)
*
* This sets up a transport channel for 9p communication. Right now
* we only match the first available channel, but eventually we couldlook up
* alternate channels by matching devname versus a virtio_config entry.
* We use a simple reference count mechanism to ensure that only a single
* mount has a channel open at a time.
*
*/
static int
p9_virtio_create(struct p9_client *client, const char *devname, char *args)
{
struct virtio_chan *chan;
int ret = -ENOENT;
int found = 0;
mutex_lock(&virtio_9p_lock);
list_for_each_entry(chan, &virtio_chan_list, chan_list) {
if (!strncmp(devname, chan->tag, chan->tag_len) &&
strlen(devname) == chan->tag_len) {
if (!chan->inuse) {
chan->inuse = true;
found = 1;
break;
}
ret = -EBUSY;
}
}
mutex_unlock(&virtio_9p_lock);
if (!found) {
pr_err("no channels available\n");
return ret;
}
client->trans = (void *)chan;
client->status = Connected;
chan->client = client;
return 0;
}
/**
* p9_virtio_remove - clean up resources associated with a virtio device
* @vdev: virtio device to remove
*
*/
static void p9_virtio_remove(struct virtio_device *vdev)
{
struct virtio_chan *chan = vdev->priv;
if (chan->inuse)
p9_virtio_close(chan->client);
vdev->config->del_vqs(vdev);
mutex_lock(&virtio_9p_lock);
list_del(&chan->chan_list);
mutex_unlock(&virtio_9p_lock);
sysfs_remove_file(&(vdev->dev.kobj), &dev_attr_mount_tag.attr);
kobject_uevent(&(vdev->dev.kobj), KOBJ_CHANGE);
kfree(chan->tag);
kfree(chan->vc_wq);
kfree(chan);
}
static struct virtio_device_id id_table[] = {
{ VIRTIO_ID_9P, VIRTIO_DEV_ANY_ID },
{ 0 },
};
static unsigned int features[] = {
VIRTIO_9P_MOUNT_TAG,
};
/* The standard "struct lguest_driver": */
static struct virtio_driver p9_virtio_drv = {
.feature_table = features,
.feature_table_size = ARRAY_SIZE(features),
.driver.name = KBUILD_MODNAME,
.driver.owner = THIS_MODULE,
.id_table = id_table,
.probe = p9_virtio_probe,
.remove = p9_virtio_remove,
};
static struct p9_trans_module p9_virtio_trans = {
.name = "virtio",
.create = p9_virtio_create,
.close = p9_virtio_close,
.request = p9_virtio_request,
.zc_request = p9_virtio_zc_request,
.cancel = p9_virtio_cancel,
/*
* We leave one entry for input and one entry for response
* headers. We also skip one more entry to accomodate, address
* that are not at page boundary, that can result in an extra
* page in zero copy.
*/
.maxsize = PAGE_SIZE * (VIRTQUEUE_NUM - 3),
.def = 0,
.owner = THIS_MODULE,
};
/* The standard init function */
static int __init p9_virtio_init(void)
{
INIT_LIST_HEAD(&virtio_chan_list);
v9fs_register_trans(&p9_virtio_trans);
return register_virtio_driver(&p9_virtio_drv);
}
static void __exit p9_virtio_cleanup(void)
{
unregister_virtio_driver(&p9_virtio_drv);
v9fs_unregister_trans(&p9_virtio_trans);
}
module_init(p9_virtio_init);
module_exit(p9_virtio_cleanup);
MODULE_DEVICE_TABLE(virtio, id_table);
MODULE_AUTHOR("Eric Van Hensbergen <ericvh@gmail.com>");
MODULE_DESCRIPTION("Virtio 9p Transport");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,141 @@
/*
* net/9p/util.c
*
* This file contains some helper functions
*
* Copyright (C) 2007 by Latchesar Ionkov <lucho@ionkov.net>
* Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
* Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/parser.h>
#include <linux/idr.h>
#include <linux/slab.h>
#include <net/9p/9p.h>
/**
* struct p9_idpool - per-connection accounting for tag idpool
* @lock: protects the pool
* @pool: idr to allocate tag id from
*
*/
struct p9_idpool {
spinlock_t lock;
struct idr pool;
};
/**
* p9_idpool_create - create a new per-connection id pool
*
*/
struct p9_idpool *p9_idpool_create(void)
{
struct p9_idpool *p;
p = kmalloc(sizeof(struct p9_idpool), GFP_KERNEL);
if (!p)
return ERR_PTR(-ENOMEM);
spin_lock_init(&p->lock);
idr_init(&p->pool);
return p;
}
EXPORT_SYMBOL(p9_idpool_create);
/**
* p9_idpool_destroy - create a new per-connection id pool
* @p: idpool to destroy
*/
void p9_idpool_destroy(struct p9_idpool *p)
{
idr_destroy(&p->pool);
kfree(p);
}
EXPORT_SYMBOL(p9_idpool_destroy);
/**
* p9_idpool_get - allocate numeric id from pool
* @p: pool to allocate from
*
* Bugs: This seems to be an awful generic function, should it be in idr.c with
* the lock included in struct idr?
*/
int p9_idpool_get(struct p9_idpool *p)
{
int i;
unsigned long flags;
idr_preload(GFP_NOFS);
spin_lock_irqsave(&p->lock, flags);
/* no need to store exactly p, we just need something non-null */
i = idr_alloc(&p->pool, p, 0, 0, GFP_NOWAIT);
spin_unlock_irqrestore(&p->lock, flags);
idr_preload_end();
if (i < 0)
return -1;
p9_debug(P9_DEBUG_MUX, " id %d pool %p\n", i, p);
return i;
}
EXPORT_SYMBOL(p9_idpool_get);
/**
* p9_idpool_put - release numeric id from pool
* @id: numeric id which is being released
* @p: pool to release id into
*
* Bugs: This seems to be an awful generic function, should it be in idr.c with
* the lock included in struct idr?
*/
void p9_idpool_put(int id, struct p9_idpool *p)
{
unsigned long flags;
p9_debug(P9_DEBUG_MUX, " id %d pool %p\n", id, p);
spin_lock_irqsave(&p->lock, flags);
idr_remove(&p->pool, id);
spin_unlock_irqrestore(&p->lock, flags);
}
EXPORT_SYMBOL(p9_idpool_put);
/**
* p9_idpool_check - check if the specified id is available
* @id: id to check
* @p: pool to check
*/
int p9_idpool_check(int id, struct p9_idpool *p)
{
return idr_find(&p->pool, id) != NULL;
}
EXPORT_SYMBOL(p9_idpool_check);

View File

@@ -0,0 +1,677 @@
/*
* linux/fs/9p/v9fs.c
*
* This file contains functions assisting in mapping VFS to 9P2000
*
* Copyright (C) 2004-2008 by Eric Van Hensbergen <ericvh@gmail.com>
* Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/parser.h>
#include <linux/idr.h>
#include <linux/slab.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
#include <net/9p/transport.h>
#include "v9fs.h"
#include "v9fs_vfs.h"
#include "cache.h"
static DEFINE_SPINLOCK(v9fs_sessionlist_lock);
static LIST_HEAD(v9fs_sessionlist);
struct kmem_cache *v9fs_inode_cache;
/*
* Option Parsing (code inspired by NFS code)
* NOTE: each transport will parse its own options
*/
enum {
/* Options that take integer arguments */
Opt_debug, Opt_dfltuid, Opt_dfltgid, Opt_afid,
/* String options */
Opt_uname, Opt_remotename, Opt_trans, Opt_cache, Opt_cachetag,
/* Options that take no arguments */
Opt_nodevmap,
/* Cache options */
Opt_cache_loose, Opt_fscache,
/* Access options */
Opt_access, Opt_posixacl,
/* Error token */
Opt_err
};
static const match_table_t tokens = {
{Opt_debug, "debug=%x"},
{Opt_dfltuid, "dfltuid=%u"},
{Opt_dfltgid, "dfltgid=%u"},
{Opt_afid, "afid=%u"},
{Opt_uname, "uname=%s"},
{Opt_remotename, "aname=%s"},
{Opt_nodevmap, "nodevmap"},
{Opt_cache, "cache=%s"},
{Opt_cache_loose, "loose"},
{Opt_fscache, "fscache"},
{Opt_cachetag, "cachetag=%s"},
{Opt_access, "access=%s"},
{Opt_posixacl, "posixacl"},
{Opt_err, NULL}
};
/* Interpret mount options for cache mode */
static int get_cache_mode(char *s)
{
int version = -EINVAL;
if (!strcmp(s, "loose")) {
version = CACHE_LOOSE;
p9_debug(P9_DEBUG_9P, "Cache mode: loose\n");
} else if (!strcmp(s, "fscache")) {
version = CACHE_FSCACHE;
p9_debug(P9_DEBUG_9P, "Cache mode: fscache\n");
} else if (!strcmp(s, "none")) {
version = CACHE_NONE;
p9_debug(P9_DEBUG_9P, "Cache mode: none\n");
} else
pr_info("Unknown Cache mode %s\n", s);
return version;
}
/**
* v9fs_parse_options - parse mount options into session structure
* @v9ses: existing v9fs session information
*
* Return 0 upon success, -ERRNO upon failure.
*/
static int v9fs_parse_options(struct v9fs_session_info *v9ses, char *opts)
{
char *options, *tmp_options;
substring_t args[MAX_OPT_ARGS];
char *p;
int option = 0;
char *s, *e;
int ret = 0;
/* setup defaults */
v9ses->afid = ~0;
v9ses->debug = 0;
v9ses->cache = CACHE_NONE;
#ifdef CONFIG_9P_FSCACHE
v9ses->cachetag = NULL;
#endif
if (!opts)
return 0;
tmp_options = kstrdup(opts, GFP_KERNEL);
if (!tmp_options) {
ret = -ENOMEM;
goto fail_option_alloc;
}
options = tmp_options;
while ((p = strsep(&options, ",")) != NULL) {
int token, r;
if (!*p)
continue;
token = match_token(p, tokens, args);
switch (token) {
case Opt_debug:
r = match_int(&args[0], &option);
if (r < 0) {
p9_debug(P9_DEBUG_ERROR,
"integer field, but no integer?\n");
ret = r;
continue;
}
v9ses->debug = option;
#ifdef CONFIG_NET_9P_DEBUG
p9_debug_level = option;
#endif
break;
case Opt_dfltuid:
r = match_int(&args[0], &option);
if (r < 0) {
p9_debug(P9_DEBUG_ERROR,
"integer field, but no integer?\n");
ret = r;
continue;
}
v9ses->dfltuid = make_kuid(current_user_ns(), option);
if (!uid_valid(v9ses->dfltuid)) {
p9_debug(P9_DEBUG_ERROR,
"uid field, but not a uid?\n");
ret = -EINVAL;
continue;
}
break;
case Opt_dfltgid:
r = match_int(&args[0], &option);
if (r < 0) {
p9_debug(P9_DEBUG_ERROR,
"integer field, but no integer?\n");
ret = r;
continue;
}
v9ses->dfltgid = make_kgid(current_user_ns(), option);
if (!gid_valid(v9ses->dfltgid)) {
p9_debug(P9_DEBUG_ERROR,
"gid field, but not a gid?\n");
ret = -EINVAL;
continue;
}
break;
case Opt_afid:
r = match_int(&args[0], &option);
if (r < 0) {
p9_debug(P9_DEBUG_ERROR,
"integer field, but no integer?\n");
ret = r;
continue;
}
v9ses->afid = option;
break;
case Opt_uname:
kfree(v9ses->uname);
v9ses->uname = match_strdup(&args[0]);
if (!v9ses->uname) {
ret = -ENOMEM;
goto free_and_return;
}
break;
case Opt_remotename:
kfree(v9ses->aname);
v9ses->aname = match_strdup(&args[0]);
if (!v9ses->aname) {
ret = -ENOMEM;
goto free_and_return;
}
break;
case Opt_nodevmap:
v9ses->nodev = 1;
break;
case Opt_cache_loose:
v9ses->cache = CACHE_LOOSE;
break;
case Opt_fscache:
v9ses->cache = CACHE_FSCACHE;
break;
case Opt_cachetag:
#ifdef CONFIG_9P_FSCACHE
v9ses->cachetag = match_strdup(&args[0]);
#endif
break;
case Opt_cache:
s = match_strdup(&args[0]);
if (!s) {
ret = -ENOMEM;
p9_debug(P9_DEBUG_ERROR,
"problem allocating copy of cache arg\n");
goto free_and_return;
}
ret = get_cache_mode(s);
if (ret == -EINVAL) {
kfree(s);
goto free_and_return;
}
v9ses->cache = ret;
kfree(s);
break;
case Opt_access:
s = match_strdup(&args[0]);
if (!s) {
ret = -ENOMEM;
p9_debug(P9_DEBUG_ERROR,
"problem allocating copy of access arg\n");
goto free_and_return;
}
v9ses->flags &= ~V9FS_ACCESS_MASK;
if (strcmp(s, "user") == 0)
v9ses->flags |= V9FS_ACCESS_USER;
else if (strcmp(s, "any") == 0)
v9ses->flags |= V9FS_ACCESS_ANY;
else if (strcmp(s, "client") == 0) {
v9ses->flags |= V9FS_ACCESS_CLIENT;
} else {
uid_t uid;
v9ses->flags |= V9FS_ACCESS_SINGLE;
uid = simple_strtoul(s, &e, 10);
if (*e != '\0') {
ret = -EINVAL;
pr_info("Unknown access argument %s\n",
s);
kfree(s);
goto free_and_return;
}
v9ses->uid = make_kuid(current_user_ns(), uid);
if (!uid_valid(v9ses->uid)) {
ret = -EINVAL;
pr_info("Uknown uid %s\n", s);
kfree(s);
goto free_and_return;
}
}
kfree(s);
break;
case Opt_posixacl:
#ifdef CONFIG_9P_FS_POSIX_ACL
v9ses->flags |= V9FS_POSIX_ACL;
#else
p9_debug(P9_DEBUG_ERROR,
"Not defined CONFIG_9P_FS_POSIX_ACL. Ignoring posixacl option\n");
#endif
break;
default:
continue;
}
}
free_and_return:
kfree(tmp_options);
fail_option_alloc:
return ret;
}
/**
* v9fs_session_init - initialize session
* @v9ses: session information structure
* @dev_name: device being mounted
* @data: options
*
*/
struct p9_fid *v9fs_session_init(struct v9fs_session_info *v9ses,
const char *dev_name, char *data)
{
int retval = -EINVAL;
struct p9_fid *fid;
int rc;
v9ses->uname = kstrdup(V9FS_DEFUSER, GFP_KERNEL);
if (!v9ses->uname)
return ERR_PTR(-ENOMEM);
v9ses->aname = kstrdup(V9FS_DEFANAME, GFP_KERNEL);
if (!v9ses->aname) {
kfree(v9ses->uname);
return ERR_PTR(-ENOMEM);
}
init_rwsem(&v9ses->rename_sem);
rc = bdi_setup_and_register(&v9ses->bdi, "9p", BDI_CAP_MAP_COPY);
if (rc) {
kfree(v9ses->aname);
kfree(v9ses->uname);
return ERR_PTR(rc);
}
spin_lock(&v9fs_sessionlist_lock);
list_add(&v9ses->slist, &v9fs_sessionlist);
spin_unlock(&v9fs_sessionlist_lock);
v9ses->uid = INVALID_UID;
v9ses->dfltuid = V9FS_DEFUID;
v9ses->dfltgid = V9FS_DEFGID;
v9ses->clnt = p9_client_create(dev_name, data);
if (IS_ERR(v9ses->clnt)) {
retval = PTR_ERR(v9ses->clnt);
v9ses->clnt = NULL;
p9_debug(P9_DEBUG_ERROR, "problem initializing 9p client\n");
goto error;
}
v9ses->flags = V9FS_ACCESS_USER;
if (p9_is_proto_dotl(v9ses->clnt)) {
v9ses->flags = V9FS_ACCESS_CLIENT;
v9ses->flags |= V9FS_PROTO_2000L;
} else if (p9_is_proto_dotu(v9ses->clnt)) {
v9ses->flags |= V9FS_PROTO_2000U;
}
rc = v9fs_parse_options(v9ses, data);
if (rc < 0) {
retval = rc;
goto error;
}
v9ses->maxdata = v9ses->clnt->msize - P9_IOHDRSZ;
if (!v9fs_proto_dotl(v9ses) &&
((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_CLIENT)) {
/*
* We support ACCESS_CLIENT only for dotl.
* Fall back to ACCESS_USER
*/
v9ses->flags &= ~V9FS_ACCESS_MASK;
v9ses->flags |= V9FS_ACCESS_USER;
}
/*FIXME !! */
/* for legacy mode, fall back to V9FS_ACCESS_ANY */
if (!(v9fs_proto_dotu(v9ses) || v9fs_proto_dotl(v9ses)) &&
((v9ses->flags&V9FS_ACCESS_MASK) == V9FS_ACCESS_USER)) {
v9ses->flags &= ~V9FS_ACCESS_MASK;
v9ses->flags |= V9FS_ACCESS_ANY;
v9ses->uid = INVALID_UID;
}
if (!v9fs_proto_dotl(v9ses) ||
!((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_CLIENT)) {
/*
* We support ACL checks on clinet only if the protocol is
* 9P2000.L and access is V9FS_ACCESS_CLIENT.
*/
v9ses->flags &= ~V9FS_ACL_MASK;
}
fid = p9_client_attach(v9ses->clnt, NULL, v9ses->uname, INVALID_UID,
v9ses->aname);
if (IS_ERR(fid)) {
retval = PTR_ERR(fid);
fid = NULL;
p9_debug(P9_DEBUG_ERROR, "cannot attach\n");
goto error;
}
if ((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_SINGLE)
fid->uid = v9ses->uid;
else
fid->uid = INVALID_UID;
#ifdef CONFIG_9P_FSCACHE
/* register the session for caching */
v9fs_cache_session_get_cookie(v9ses);
#endif
return fid;
error:
bdi_destroy(&v9ses->bdi);
return ERR_PTR(retval);
}
/**
* v9fs_session_close - shutdown a session
* @v9ses: session information structure
*
*/
void v9fs_session_close(struct v9fs_session_info *v9ses)
{
if (v9ses->clnt) {
p9_client_destroy(v9ses->clnt);
v9ses->clnt = NULL;
}
#ifdef CONFIG_9P_FSCACHE
if (v9ses->fscache) {
v9fs_cache_session_put_cookie(v9ses);
kfree(v9ses->cachetag);
}
#endif
kfree(v9ses->uname);
kfree(v9ses->aname);
bdi_destroy(&v9ses->bdi);
spin_lock(&v9fs_sessionlist_lock);
list_del(&v9ses->slist);
spin_unlock(&v9fs_sessionlist_lock);
}
/**
* v9fs_session_cancel - terminate a session
* @v9ses: session to terminate
*
* mark transport as disconnected and cancel all pending requests.
*/
void v9fs_session_cancel(struct v9fs_session_info *v9ses) {
p9_debug(P9_DEBUG_ERROR, "cancel session %p\n", v9ses);
p9_client_disconnect(v9ses->clnt);
}
/**
* v9fs_session_begin_cancel - Begin terminate of a session
* @v9ses: session to terminate
*
* After this call we don't allow any request other than clunk.
*/
void v9fs_session_begin_cancel(struct v9fs_session_info *v9ses)
{
p9_debug(P9_DEBUG_ERROR, "begin cancel session %p\n", v9ses);
p9_client_begin_disconnect(v9ses->clnt);
}
extern int v9fs_error_init(void);
static struct kobject *v9fs_kobj;
#ifdef CONFIG_9P_FSCACHE
/**
* caches_show - list caches associated with a session
*
* Returns the size of buffer written.
*/
static ssize_t caches_show(struct kobject *kobj,
struct kobj_attribute *attr,
char *buf)
{
ssize_t n = 0, count = 0, limit = PAGE_SIZE;
struct v9fs_session_info *v9ses;
spin_lock(&v9fs_sessionlist_lock);
list_for_each_entry(v9ses, &v9fs_sessionlist, slist) {
if (v9ses->cachetag) {
n = snprintf(buf, limit, "%s\n", v9ses->cachetag);
if (n < 0) {
count = n;
break;
}
count += n;
limit -= n;
}
}
spin_unlock(&v9fs_sessionlist_lock);
return count;
}
static struct kobj_attribute v9fs_attr_cache = __ATTR_RO(caches);
#endif /* CONFIG_9P_FSCACHE */
static struct attribute *v9fs_attrs[] = {
#ifdef CONFIG_9P_FSCACHE
&v9fs_attr_cache.attr,
#endif
NULL,
};
static struct attribute_group v9fs_attr_group = {
.attrs = v9fs_attrs,
};
/**
* v9fs_sysfs_init - Initialize the v9fs sysfs interface
*
*/
static int v9fs_sysfs_init(void)
{
v9fs_kobj = kobject_create_and_add("9p", fs_kobj);
if (!v9fs_kobj)
return -ENOMEM;
if (sysfs_create_group(v9fs_kobj, &v9fs_attr_group)) {
kobject_put(v9fs_kobj);
return -ENOMEM;
}
return 0;
}
/**
* v9fs_sysfs_cleanup - Unregister the v9fs sysfs interface
*
*/
static void v9fs_sysfs_cleanup(void)
{
sysfs_remove_group(v9fs_kobj, &v9fs_attr_group);
kobject_put(v9fs_kobj);
}
static void v9fs_inode_init_once(void *foo)
{
struct v9fs_inode *v9inode = (struct v9fs_inode *)foo;
#ifdef CONFIG_9P_FSCACHE
v9inode->fscache = NULL;
#endif
memset(&v9inode->qid, 0, sizeof(v9inode->qid));
inode_init_once(&v9inode->vfs_inode);
}
/**
* v9fs_init_inode_cache - initialize a cache for 9P
* Returns 0 on success.
*/
static int v9fs_init_inode_cache(void)
{
v9fs_inode_cache = kmem_cache_create("v9fs_inode_cache",
sizeof(struct v9fs_inode),
0, (SLAB_RECLAIM_ACCOUNT|
SLAB_MEM_SPREAD),
v9fs_inode_init_once);
if (!v9fs_inode_cache)
return -ENOMEM;
return 0;
}
/**
* v9fs_destroy_inode_cache - destroy the cache of 9P inode
*
*/
static void v9fs_destroy_inode_cache(void)
{
/*
* Make sure all delayed rcu free inodes are flushed before we
* destroy cache.
*/
rcu_barrier();
kmem_cache_destroy(v9fs_inode_cache);
}
static int v9fs_cache_register(void)
{
int ret;
ret = v9fs_init_inode_cache();
if (ret < 0)
return ret;
#ifdef CONFIG_9P_FSCACHE
return fscache_register_netfs(&v9fs_cache_netfs);
#else
return ret;
#endif
}
static void v9fs_cache_unregister(void)
{
v9fs_destroy_inode_cache();
#ifdef CONFIG_9P_FSCACHE
fscache_unregister_netfs(&v9fs_cache_netfs);
#endif
}
/**
* init_v9fs - Initialize module
*
*/
static int __init init_v9fs(void)
{
int err;
pr_info("Installing v9fs 9p2000 file system support\n");
/* TODO: Setup list of registered trasnport modules */
err = v9fs_cache_register();
if (err < 0) {
pr_err("Failed to register v9fs for caching\n");
return err;
}
err = v9fs_sysfs_init();
if (err < 0) {
pr_err("Failed to register with sysfs\n");
goto out_cache;
}
err = register_filesystem(&v9fs_fs_type);
if (err < 0) {
pr_err("Failed to register filesystem\n");
goto out_sysfs_cleanup;
}
return 0;
out_sysfs_cleanup:
v9fs_sysfs_cleanup();
out_cache:
v9fs_cache_unregister();
return err;
}
/**
* exit_v9fs - shutdown module
*
*/
static void __exit exit_v9fs(void)
{
v9fs_sysfs_cleanup();
v9fs_cache_unregister();
unregister_filesystem(&v9fs_fs_type);
}
module_init(init_v9fs)
module_exit(exit_v9fs)
MODULE_AUTHOR("Latchesar Ionkov <lucho@ionkov.net>");
MODULE_AUTHOR("Eric Van Hensbergen <ericvh@gmail.com>");
MODULE_AUTHOR("Ron Minnich <rminnich@lanl.gov>");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,227 @@
/*
* V9FS definitions.
*
* Copyright (C) 2004-2008 by Eric Van Hensbergen <ericvh@gmail.com>
* Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#ifndef FS_9P_V9FS_H
#define FS_9P_V9FS_H
#include <linux/backing-dev.h>
/**
* enum p9_session_flags - option flags for each 9P session
* @V9FS_PROTO_2000U: whether or not to use 9P2000.u extensions
* @V9FS_PROTO_2000L: whether or not to use 9P2000.l extensions
* @V9FS_ACCESS_SINGLE: only the mounting user can access the hierarchy
* @V9FS_ACCESS_USER: a new attach will be issued for every user (default)
* @V9FS_ACCESS_CLIENT: Just like user, but access check is performed on client.
* @V9FS_ACCESS_ANY: use a single attach for all users
* @V9FS_ACCESS_MASK: bit mask of different ACCESS options
* @V9FS_POSIX_ACL: POSIX ACLs are enforced
*
* Session flags reflect options selected by users at mount time
*/
#define V9FS_ACCESS_ANY (V9FS_ACCESS_SINGLE | \
V9FS_ACCESS_USER | \
V9FS_ACCESS_CLIENT)
#define V9FS_ACCESS_MASK V9FS_ACCESS_ANY
#define V9FS_ACL_MASK V9FS_POSIX_ACL
enum p9_session_flags {
V9FS_PROTO_2000U = 0x01,
V9FS_PROTO_2000L = 0x02,
V9FS_ACCESS_SINGLE = 0x04,
V9FS_ACCESS_USER = 0x08,
V9FS_ACCESS_CLIENT = 0x10,
V9FS_POSIX_ACL = 0x20
};
/* possible values of ->cache */
/**
* enum p9_cache_modes - user specified cache preferences
* @CACHE_NONE: do not cache data, dentries, or directory contents (default)
* @CACHE_LOOSE: cache data, dentries, and directory contents w/no consistency
*
* eventually support loose, tight, time, session, default always none
*/
enum p9_cache_modes {
CACHE_NONE,
CACHE_LOOSE,
CACHE_FSCACHE,
};
/**
* struct v9fs_session_info - per-instance session information
* @flags: session options of type &p9_session_flags
* @nodev: set to 1 to disable device mapping
* @debug: debug level
* @afid: authentication handle
* @cache: cache mode of type &p9_cache_modes
* @cachetag: the tag of the cache associated with this session
* @fscache: session cookie associated with FS-Cache
* @options: copy of options string given by user
* @uname: string user name to mount hierarchy as
* @aname: mount specifier for remote hierarchy
* @maxdata: maximum data to be sent/recvd per protocol message
* @dfltuid: default numeric userid to mount hierarchy as
* @dfltgid: default numeric groupid to mount hierarchy as
* @uid: if %V9FS_ACCESS_SINGLE, the numeric uid which mounted the hierarchy
* @clnt: reference to 9P network client instantiated for this session
* @slist: reference to list of registered 9p sessions
*
* This structure holds state for each session instance established during
* a sys_mount() .
*
* Bugs: there seems to be a lot of state which could be condensed and/or
* removed.
*/
struct v9fs_session_info {
/* options */
unsigned char flags;
unsigned char nodev;
unsigned short debug;
unsigned int afid;
unsigned int cache;
#ifdef CONFIG_9P_FSCACHE
char *cachetag;
struct fscache_cookie *fscache;
#endif
char *uname; /* user name to mount as */
char *aname; /* name of remote hierarchy being mounted */
unsigned int maxdata; /* max data for client interface */
kuid_t dfltuid; /* default uid/muid for legacy support */
kgid_t dfltgid; /* default gid for legacy support */
kuid_t uid; /* if ACCESS_SINGLE, the uid that has access */
struct p9_client *clnt; /* 9p client */
struct list_head slist; /* list of sessions registered with v9fs */
struct backing_dev_info bdi;
struct rw_semaphore rename_sem;
};
/* cache_validity flags */
#define V9FS_INO_INVALID_ATTR 0x01
struct v9fs_inode {
#ifdef CONFIG_9P_FSCACHE
spinlock_t fscache_lock;
struct fscache_cookie *fscache;
#endif
struct p9_qid qid;
unsigned int cache_validity;
struct p9_fid *writeback_fid;
struct mutex v_mutex;
struct inode vfs_inode;
};
static inline struct v9fs_inode *V9FS_I(const struct inode *inode)
{
return container_of(inode, struct v9fs_inode, vfs_inode);
}
struct p9_fid *v9fs_session_init(struct v9fs_session_info *, const char *,
char *);
extern void v9fs_session_close(struct v9fs_session_info *v9ses);
extern void v9fs_session_cancel(struct v9fs_session_info *v9ses);
extern void v9fs_session_begin_cancel(struct v9fs_session_info *v9ses);
extern struct dentry *v9fs_vfs_lookup(struct inode *dir, struct dentry *dentry,
unsigned int flags);
extern int v9fs_vfs_unlink(struct inode *i, struct dentry *d);
extern int v9fs_vfs_rmdir(struct inode *i, struct dentry *d);
extern int v9fs_vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
struct inode *new_dir, struct dentry *new_dentry);
extern void v9fs_vfs_put_link(struct dentry *dentry, struct nameidata *nd,
void *p);
extern struct inode *v9fs_inode_from_fid(struct v9fs_session_info *v9ses,
struct p9_fid *fid,
struct super_block *sb, int new);
extern const struct inode_operations v9fs_dir_inode_operations_dotl;
extern const struct inode_operations v9fs_file_inode_operations_dotl;
extern const struct inode_operations v9fs_symlink_inode_operations_dotl;
extern struct inode *v9fs_inode_from_fid_dotl(struct v9fs_session_info *v9ses,
struct p9_fid *fid,
struct super_block *sb, int new);
/* other default globals */
#define V9FS_PORT 564
#define V9FS_DEFUSER "nobody"
#define V9FS_DEFANAME ""
#define V9FS_DEFUID KUIDT_INIT(-2)
#define V9FS_DEFGID KGIDT_INIT(-2)
static inline struct v9fs_session_info *v9fs_inode2v9ses(struct inode *inode)
{
return (inode->i_sb->s_fs_info);
}
static inline struct v9fs_session_info *v9fs_dentry2v9ses(struct dentry *dentry)
{
return dentry->d_sb->s_fs_info;
}
static inline int v9fs_proto_dotu(struct v9fs_session_info *v9ses)
{
return v9ses->flags & V9FS_PROTO_2000U;
}
static inline int v9fs_proto_dotl(struct v9fs_session_info *v9ses)
{
return v9ses->flags & V9FS_PROTO_2000L;
}
/**
* v9fs_get_inode_from_fid - Helper routine to populate an inode by
* issuing a attribute request
* @v9ses: session information
* @fid: fid to issue attribute request for
* @sb: superblock on which to create inode
*
*/
static inline struct inode *
v9fs_get_inode_from_fid(struct v9fs_session_info *v9ses, struct p9_fid *fid,
struct super_block *sb)
{
if (v9fs_proto_dotl(v9ses))
return v9fs_inode_from_fid_dotl(v9ses, fid, sb, 0);
else
return v9fs_inode_from_fid(v9ses, fid, sb, 0);
}
/**
* v9fs_get_new_inode_from_fid - Helper routine to populate an inode by
* issuing a attribute request
* @v9ses: session information
* @fid: fid to issue attribute request for
* @sb: superblock on which to create inode
*
*/
static inline struct inode *
v9fs_get_new_inode_from_fid(struct v9fs_session_info *v9ses, struct p9_fid *fid,
struct super_block *sb)
{
if (v9fs_proto_dotl(v9ses))
return v9fs_inode_from_fid_dotl(v9ses, fid, sb, 1);
else
return v9fs_inode_from_fid(v9ses, fid, sb, 1);
}
#endif

View File

@@ -0,0 +1,88 @@
/*
* V9FS VFS extensions.
*
* Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
* Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#ifndef FS_9P_V9FS_VFS_H
#define FS_9P_V9FS_VFS_H
/* plan9 semantics are that created files are implicitly opened.
* But linux semantics are that you call create, then open.
* the plan9 approach is superior as it provides an atomic
* open.
* we track the create fid here. When the file is opened, if fidopen is
* non-zero, we use the fid and can skip some steps.
* there may be a better way to do this, but I don't know it.
* one BAD way is to clunk the fid on create, then open it again:
* you lose the atomicity of file open
*/
/* special case:
* unlink calls remove, which is an implicit clunk. So we have to track
* that kind of thing so that we don't try to clunk a dead fid.
*/
#define P9_LOCK_TIMEOUT (30*HZ)
extern struct file_system_type v9fs_fs_type;
extern const struct address_space_operations v9fs_addr_operations;
extern const struct file_operations v9fs_file_operations;
extern const struct file_operations v9fs_file_operations_dotl;
extern const struct file_operations v9fs_dir_operations;
extern const struct file_operations v9fs_dir_operations_dotl;
extern const struct dentry_operations v9fs_dentry_operations;
extern const struct dentry_operations v9fs_cached_dentry_operations;
extern const struct file_operations v9fs_cached_file_operations;
extern const struct file_operations v9fs_cached_file_operations_dotl;
extern struct kmem_cache *v9fs_inode_cache;
struct inode *v9fs_alloc_inode(struct super_block *sb);
void v9fs_destroy_inode(struct inode *inode);
struct inode *v9fs_get_inode(struct super_block *sb, umode_t mode, dev_t);
int v9fs_init_inode(struct v9fs_session_info *v9ses,
struct inode *inode, umode_t mode, dev_t);
void v9fs_evict_inode(struct inode *inode);
ino_t v9fs_qid2ino(struct p9_qid *qid);
void v9fs_stat2inode(struct p9_wstat *, struct inode *, struct super_block *);
void v9fs_stat2inode_dotl(struct p9_stat_dotl *, struct inode *);
int v9fs_dir_release(struct inode *inode, struct file *filp);
int v9fs_file_open(struct inode *inode, struct file *file);
void v9fs_inode2stat(struct inode *inode, struct p9_wstat *stat);
int v9fs_uflags2omode(int uflags, int extended);
ssize_t v9fs_file_readn(struct file *, char *, char __user *, u32, u64);
ssize_t v9fs_fid_readn(struct p9_fid *, char *, char __user *, u32, u64);
void v9fs_blank_wstat(struct p9_wstat *wstat);
int v9fs_vfs_setattr_dotl(struct dentry *, struct iattr *);
int v9fs_file_fsync_dotl(struct file *filp, loff_t start, loff_t end,
int datasync);
ssize_t v9fs_file_write_internal(struct inode *, struct p9_fid *,
const char __user *, size_t, loff_t *, int);
int v9fs_refresh_inode(struct p9_fid *fid, struct inode *inode);
int v9fs_refresh_inode_dotl(struct p9_fid *fid, struct inode *inode);
static inline void v9fs_invalidate_inode_attr(struct inode *inode)
{
struct v9fs_inode *v9inode;
v9inode = V9FS_I(inode);
v9inode->cache_validity |= V9FS_INO_INVALID_ATTR;
return;
}
int v9fs_open_to_dotl_flags(int flags);
#endif

View File

@@ -0,0 +1,353 @@
/*
* linux/fs/9p/vfs_addr.c
*
* This file contians vfs address (mmap) ops for 9P2000.
*
* Copyright (C) 2005 by Eric Van Hensbergen <ericvh@gmail.com>
* Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/stat.h>
#include <linux/string.h>
#include <linux/inet.h>
#include <linux/pagemap.h>
#include <linux/idr.h>
#include <linux/sched.h>
#include <linux/aio.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
#include "v9fs.h"
#include "v9fs_vfs.h"
#include "cache.h"
#include "fid.h"
/**
* v9fs_fid_readpage - read an entire page in from 9P
*
* @fid: fid being read
* @page: structure to page
*
*/
static int v9fs_fid_readpage(struct p9_fid *fid, struct page *page)
{
int retval;
loff_t offset;
char *buffer;
struct inode *inode;
inode = page->mapping->host;
p9_debug(P9_DEBUG_VFS, "\n");
BUG_ON(!PageLocked(page));
retval = v9fs_readpage_from_fscache(inode, page);
if (retval == 0)
return retval;
buffer = kmap(page);
offset = page_offset(page);
retval = v9fs_fid_readn(fid, buffer, NULL, PAGE_CACHE_SIZE, offset);
if (retval < 0) {
v9fs_uncache_page(inode, page);
goto done;
}
memset(buffer + retval, 0, PAGE_CACHE_SIZE - retval);
flush_dcache_page(page);
SetPageUptodate(page);
v9fs_readpage_to_fscache(inode, page);
retval = 0;
done:
kunmap(page);
unlock_page(page);
return retval;
}
/**
* v9fs_vfs_readpage - read an entire page in from 9P
*
* @filp: file being read
* @page: structure to page
*
*/
static int v9fs_vfs_readpage(struct file *filp, struct page *page)
{
return v9fs_fid_readpage(filp->private_data, page);
}
/**
* v9fs_vfs_readpages - read a set of pages from 9P
*
* @filp: file being read
* @mapping: the address space
* @pages: list of pages to read
* @nr_pages: count of pages to read
*
*/
static int v9fs_vfs_readpages(struct file *filp, struct address_space *mapping,
struct list_head *pages, unsigned nr_pages)
{
int ret = 0;
struct inode *inode;
inode = mapping->host;
p9_debug(P9_DEBUG_VFS, "inode: %p file: %p\n", inode, filp);
ret = v9fs_readpages_from_fscache(inode, mapping, pages, &nr_pages);
if (ret == 0)
return ret;
ret = read_cache_pages(mapping, pages, (void *)v9fs_vfs_readpage, filp);
p9_debug(P9_DEBUG_VFS, " = %d\n", ret);
return ret;
}
/**
* v9fs_release_page - release the private state associated with a page
*
* Returns 1 if the page can be released, false otherwise.
*/
static int v9fs_release_page(struct page *page, gfp_t gfp)
{
if (PagePrivate(page))
return 0;
return v9fs_fscache_release_page(page, gfp);
}
/**
* v9fs_invalidate_page - Invalidate a page completely or partially
*
* @page: structure to page
* @offset: offset in the page
*/
static void v9fs_invalidate_page(struct page *page, unsigned long offset)
{
/*
* If called with zero offset, we should release
* the private state assocated with the page
*/
if (offset == 0)
v9fs_fscache_invalidate_page(page);
}
static int v9fs_vfs_writepage_locked(struct page *page)
{
char *buffer;
int retval, len;
loff_t offset, size;
mm_segment_t old_fs;
struct v9fs_inode *v9inode;
struct inode *inode = page->mapping->host;
v9inode = V9FS_I(inode);
size = i_size_read(inode);
if (page->index == size >> PAGE_CACHE_SHIFT)
len = size & ~PAGE_CACHE_MASK;
else
len = PAGE_CACHE_SIZE;
set_page_writeback(page);
buffer = kmap(page);
offset = page_offset(page);
old_fs = get_fs();
set_fs(get_ds());
/* We should have writeback_fid always set */
BUG_ON(!v9inode->writeback_fid);
retval = v9fs_file_write_internal(inode,
v9inode->writeback_fid,
(__force const char __user *)buffer,
len, &offset, 0);
if (retval > 0)
retval = 0;
set_fs(old_fs);
kunmap(page);
end_page_writeback(page);
return retval;
}
static int v9fs_vfs_writepage(struct page *page, struct writeback_control *wbc)
{
int retval;
retval = v9fs_vfs_writepage_locked(page);
if (retval < 0) {
if (retval == -EAGAIN) {
redirty_page_for_writepage(wbc, page);
retval = 0;
} else {
SetPageError(page);
mapping_set_error(page->mapping, retval);
}
} else
retval = 0;
unlock_page(page);
return retval;
}
/**
* v9fs_launder_page - Writeback a dirty page
* Returns 0 on success.
*/
static int v9fs_launder_page(struct page *page)
{
int retval;
struct inode *inode = page->mapping->host;
v9fs_fscache_wait_on_page_write(inode, page);
if (clear_page_dirty_for_io(page)) {
retval = v9fs_vfs_writepage_locked(page);
if (retval)
return retval;
}
return 0;
}
/**
* v9fs_direct_IO - 9P address space operation for direct I/O
* @rw: direction (read or write)
* @iocb: target I/O control block
* @iov: array of vectors that define I/O buffer
* @pos: offset in file to begin the operation
* @nr_segs: size of iovec array
*
* The presence of v9fs_direct_IO() in the address space ops vector
* allowes open() O_DIRECT flags which would have failed otherwise.
*
* In the non-cached mode, we shunt off direct read and write requests before
* the VFS gets them, so this method should never be called.
*
* Direct IO is not 'yet' supported in the cached mode. Hence when
* this routine is called through generic_file_aio_read(), the read/write fails
* with an error.
*
*/
static ssize_t
v9fs_direct_IO(int rw, struct kiocb *iocb, const struct iovec *iov,
loff_t pos, unsigned long nr_segs)
{
/*
* FIXME
* Now that we do caching with cache mode enabled, We need
* to support direct IO
*/
p9_debug(P9_DEBUG_VFS, "v9fs_direct_IO: v9fs_direct_IO (%s) off/no(%lld/%lu) EINVAL\n",
iocb->ki_filp->f_path.dentry->d_name.name,
(long long)pos, nr_segs);
return -EINVAL;
}
static int v9fs_write_begin(struct file *filp, struct address_space *mapping,
loff_t pos, unsigned len, unsigned flags,
struct page **pagep, void **fsdata)
{
int retval = 0;
struct page *page;
struct v9fs_inode *v9inode;
pgoff_t index = pos >> PAGE_CACHE_SHIFT;
struct inode *inode = mapping->host;
v9inode = V9FS_I(inode);
start:
page = grab_cache_page_write_begin(mapping, index, flags);
if (!page) {
retval = -ENOMEM;
goto out;
}
BUG_ON(!v9inode->writeback_fid);
if (PageUptodate(page))
goto out;
if (len == PAGE_CACHE_SIZE)
goto out;
retval = v9fs_fid_readpage(v9inode->writeback_fid, page);
page_cache_release(page);
if (!retval)
goto start;
out:
*pagep = page;
return retval;
}
static int v9fs_write_end(struct file *filp, struct address_space *mapping,
loff_t pos, unsigned len, unsigned copied,
struct page *page, void *fsdata)
{
loff_t last_pos = pos + copied;
struct inode *inode = page->mapping->host;
if (unlikely(copied < len)) {
/*
* zero out the rest of the area
*/
unsigned from = pos & (PAGE_CACHE_SIZE - 1);
zero_user(page, from + copied, len - copied);
flush_dcache_page(page);
}
if (!PageUptodate(page))
SetPageUptodate(page);
/*
* No need to use i_size_read() here, the i_size
* cannot change under us because we hold the i_mutex.
*/
if (last_pos > inode->i_size) {
inode_add_bytes(inode, last_pos - inode->i_size);
i_size_write(inode, last_pos);
}
set_page_dirty(page);
unlock_page(page);
page_cache_release(page);
return copied;
}
const struct address_space_operations v9fs_addr_operations = {
.readpage = v9fs_vfs_readpage,
.readpages = v9fs_vfs_readpages,
.set_page_dirty = __set_page_dirty_nobuffers,
.writepage = v9fs_vfs_writepage,
.write_begin = v9fs_write_begin,
.write_end = v9fs_write_end,
.releasepage = v9fs_release_page,
.invalidatepage = v9fs_invalidate_page,
.launder_page = v9fs_launder_page,
.direct_IO = v9fs_direct_IO,
};

View File

@@ -0,0 +1,139 @@
/*
* linux/fs/9p/vfs_dentry.c
*
* This file contians vfs dentry ops for the 9P2000 protocol.
*
* Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
* Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/pagemap.h>
#include <linux/stat.h>
#include <linux/string.h>
#include <linux/inet.h>
#include <linux/namei.h>
#include <linux/idr.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
#include "v9fs.h"
#include "v9fs_vfs.h"
#include "fid.h"
/**
* v9fs_dentry_delete - called when dentry refcount equals 0
* @dentry: dentry in question
*
* By returning 1 here we should remove cacheing of unused
* dentry components.
*
*/
static int v9fs_dentry_delete(const struct dentry *dentry)
{
p9_debug(P9_DEBUG_VFS, " dentry: %s (%p)\n",
dentry->d_name.name, dentry);
return 1;
}
/**
* v9fs_cached_dentry_delete - called when dentry refcount equals 0
* @dentry: dentry in question
*
*/
static int v9fs_cached_dentry_delete(const struct dentry *dentry)
{
p9_debug(P9_DEBUG_VFS, " dentry: %s (%p)\n",
dentry->d_name.name, dentry);
/* Don't cache negative dentries */
if (!dentry->d_inode)
return 1;
return 0;
}
/**
* v9fs_dentry_release - called when dentry is going to be freed
* @dentry: dentry that is being release
*
*/
static void v9fs_dentry_release(struct dentry *dentry)
{
struct hlist_node *p, *n;
p9_debug(P9_DEBUG_VFS, " dentry: %s (%p)\n",
dentry->d_name.name, dentry);
hlist_for_each_safe(p, n, (struct hlist_head *)&dentry->d_fsdata)
p9_client_clunk(hlist_entry(p, struct p9_fid, dlist));
dentry->d_fsdata = NULL;
}
static int v9fs_lookup_revalidate(struct dentry *dentry, unsigned int flags)
{
struct p9_fid *fid;
struct inode *inode;
struct v9fs_inode *v9inode;
if (flags & LOOKUP_RCU)
return -ECHILD;
inode = dentry->d_inode;
if (!inode)
goto out_valid;
v9inode = V9FS_I(inode);
if (v9inode->cache_validity & V9FS_INO_INVALID_ATTR) {
int retval;
struct v9fs_session_info *v9ses;
fid = v9fs_fid_lookup(dentry);
if (IS_ERR(fid))
return PTR_ERR(fid);
v9ses = v9fs_inode2v9ses(inode);
if (v9fs_proto_dotl(v9ses))
retval = v9fs_refresh_inode_dotl(fid, inode);
else
retval = v9fs_refresh_inode(fid, inode);
if (retval == -ENOENT)
return 0;
if (retval < 0)
return retval;
}
out_valid:
return 1;
}
const struct dentry_operations v9fs_cached_dentry_operations = {
.d_revalidate = v9fs_lookup_revalidate,
.d_weak_revalidate = v9fs_lookup_revalidate,
.d_delete = v9fs_cached_dentry_delete,
.d_release = v9fs_dentry_release,
};
const struct dentry_operations v9fs_dentry_operations = {
.d_delete = v9fs_dentry_delete,
.d_release = v9fs_dentry_release,
};

View File

@@ -0,0 +1,269 @@
/*
* linux/fs/9p/vfs_dir.c
*
* This file contains vfs directory ops for the 9P2000 protocol.
*
* Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
* Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/stat.h>
#include <linux/string.h>
#include <linux/sched.h>
#include <linux/inet.h>
#include <linux/idr.h>
#include <linux/slab.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
#include "v9fs.h"
#include "v9fs_vfs.h"
#include "fid.h"
/**
* struct p9_rdir - readdir accounting
* @mutex: mutex protecting readdir
* @head: start offset of current dirread buffer
* @tail: end offset of current dirread buffer
* @buf: dirread buffer
*
* private structure for keeping track of readdir
* allocated on demand
*/
struct p9_rdir {
int head;
int tail;
uint8_t buf[];
};
/**
* dt_type - return file type
* @mistat: mistat structure
*
*/
static inline int dt_type(struct p9_wstat *mistat)
{
unsigned long perm = mistat->mode;
int rettype = DT_REG;
if (perm & P9_DMDIR)
rettype = DT_DIR;
if (perm & P9_DMSYMLINK)
rettype = DT_LNK;
return rettype;
}
static void p9stat_init(struct p9_wstat *stbuf)
{
stbuf->name = NULL;
stbuf->uid = NULL;
stbuf->gid = NULL;
stbuf->muid = NULL;
stbuf->extension = NULL;
}
/**
* v9fs_alloc_rdir_buf - Allocate buffer used for read and readdir
* @filp: opened file structure
* @buflen: Length in bytes of buffer to allocate
*
*/
static struct p9_rdir *v9fs_alloc_rdir_buf(struct file *filp, int buflen)
{
struct p9_fid *fid = filp->private_data;
if (!fid->rdir)
fid->rdir = kzalloc(sizeof(struct p9_rdir) + buflen, GFP_KERNEL);
return fid->rdir;
}
/**
* v9fs_dir_readdir - read a directory
* @filp: opened file structure
* @dirent: directory structure ???
* @filldir: function to populate directory structure ???
*
*/
static int v9fs_dir_readdir(struct file *filp, void *dirent, filldir_t filldir)
{
int over;
struct p9_wstat st;
int err = 0;
struct p9_fid *fid;
int buflen;
int reclen = 0;
struct p9_rdir *rdir;
p9_debug(P9_DEBUG_VFS, "name %s\n", filp->f_path.dentry->d_name.name);
fid = filp->private_data;
buflen = fid->clnt->msize - P9_IOHDRSZ;
rdir = v9fs_alloc_rdir_buf(filp, buflen);
if (!rdir)
return -ENOMEM;
while (1) {
if (rdir->tail == rdir->head) {
err = v9fs_file_readn(filp, rdir->buf, NULL,
buflen, filp->f_pos);
if (err <= 0)
return err;
rdir->head = 0;
rdir->tail = err;
}
while (rdir->head < rdir->tail) {
p9stat_init(&st);
err = p9stat_read(fid->clnt, rdir->buf + rdir->head,
rdir->tail - rdir->head, &st);
if (err) {
p9_debug(P9_DEBUG_VFS, "returned %d\n", err);
p9stat_free(&st);
return -EIO;
}
reclen = st.size+2;
over = filldir(dirent, st.name, strlen(st.name),
filp->f_pos, v9fs_qid2ino(&st.qid), dt_type(&st));
p9stat_free(&st);
if (over)
return 0;
rdir->head += reclen;
filp->f_pos += reclen;
}
}
}
/**
* v9fs_dir_readdir_dotl - read a directory
* @filp: opened file structure
* @dirent: buffer to fill dirent structures
* @filldir: function to populate dirent structures
*
*/
static int v9fs_dir_readdir_dotl(struct file *filp, void *dirent,
filldir_t filldir)
{
int over;
int err = 0;
struct p9_fid *fid;
int buflen;
struct p9_rdir *rdir;
struct p9_dirent curdirent;
u64 oldoffset = 0;
p9_debug(P9_DEBUG_VFS, "name %s\n", filp->f_path.dentry->d_name.name);
fid = filp->private_data;
buflen = fid->clnt->msize - P9_READDIRHDRSZ;
rdir = v9fs_alloc_rdir_buf(filp, buflen);
if (!rdir)
return -ENOMEM;
while (1) {
if (rdir->tail == rdir->head) {
err = p9_client_readdir(fid, rdir->buf, buflen,
filp->f_pos);
if (err <= 0)
return err;
rdir->head = 0;
rdir->tail = err;
}
while (rdir->head < rdir->tail) {
err = p9dirent_read(fid->clnt, rdir->buf + rdir->head,
rdir->tail - rdir->head,
&curdirent);
if (err < 0) {
p9_debug(P9_DEBUG_VFS, "returned %d\n", err);
return -EIO;
}
/* d_off in dirent structure tracks the offset into
* the next dirent in the dir. However, filldir()
* expects offset into the current dirent. Hence
* while calling filldir send the offset from the
* previous dirent structure.
*/
over = filldir(dirent, curdirent.d_name,
strlen(curdirent.d_name),
oldoffset, v9fs_qid2ino(&curdirent.qid),
curdirent.d_type);
oldoffset = curdirent.d_off;
if (over)
return 0;
filp->f_pos = curdirent.d_off;
rdir->head += err;
}
}
}
/**
* v9fs_dir_release - close a directory
* @inode: inode of the directory
* @filp: file pointer to a directory
*
*/
int v9fs_dir_release(struct inode *inode, struct file *filp)
{
struct p9_fid *fid;
fid = filp->private_data;
p9_debug(P9_DEBUG_VFS, "inode: %p filp: %p fid: %d\n",
inode, filp, fid ? fid->fid : -1);
if (fid)
p9_client_clunk(fid);
return 0;
}
const struct file_operations v9fs_dir_operations = {
.read = generic_read_dir,
.llseek = generic_file_llseek,
.readdir = v9fs_dir_readdir,
.open = v9fs_file_open,
.release = v9fs_dir_release,
};
const struct file_operations v9fs_dir_operations_dotl = {
.read = generic_read_dir,
.llseek = generic_file_llseek,
.readdir = v9fs_dir_readdir_dotl,
.open = v9fs_file_open,
.release = v9fs_dir_release,
.fsync = v9fs_file_fsync_dotl,
};

View File

@@ -0,0 +1,790 @@
/*
* linux/fs/9p/vfs_file.c
*
* This file contians vfs file ops for 9P2000.
*
* Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
* Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/file.h>
#include <linux/stat.h>
#include <linux/string.h>
#include <linux/inet.h>
#include <linux/list.h>
#include <linux/pagemap.h>
#include <linux/utsname.h>
#include <asm/uaccess.h>
#include <linux/idr.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
#include "v9fs.h"
#include "v9fs_vfs.h"
#include "fid.h"
#include "cache.h"
static const struct vm_operations_struct v9fs_file_vm_ops;
/**
* v9fs_file_open - open a file (or directory)
* @inode: inode to be opened
* @file: file being opened
*
*/
int v9fs_file_open(struct inode *inode, struct file *file)
{
int err;
struct v9fs_inode *v9inode;
struct v9fs_session_info *v9ses;
struct p9_fid *fid;
int omode;
p9_debug(P9_DEBUG_VFS, "inode: %p file: %p\n", inode, file);
v9inode = V9FS_I(inode);
v9ses = v9fs_inode2v9ses(inode);
if (v9fs_proto_dotl(v9ses))
omode = v9fs_open_to_dotl_flags(file->f_flags);
else
omode = v9fs_uflags2omode(file->f_flags,
v9fs_proto_dotu(v9ses));
fid = file->private_data;
if (!fid) {
fid = v9fs_fid_clone(file->f_path.dentry);
if (IS_ERR(fid))
return PTR_ERR(fid);
err = p9_client_open(fid, omode);
if (err < 0) {
p9_client_clunk(fid);
return err;
}
if ((file->f_flags & O_APPEND) &&
(!v9fs_proto_dotu(v9ses) && !v9fs_proto_dotl(v9ses)))
generic_file_llseek(file, 0, SEEK_END);
}
file->private_data = fid;
mutex_lock(&v9inode->v_mutex);
if (v9ses->cache && !v9inode->writeback_fid &&
((file->f_flags & O_ACCMODE) != O_RDONLY)) {
/*
* clone a fid and add it to writeback_fid
* we do it during open time instead of
* page dirty time via write_begin/page_mkwrite
* because we want write after unlink usecase
* to work.
*/
fid = v9fs_writeback_fid(file->f_path.dentry);
if (IS_ERR(fid)) {
err = PTR_ERR(fid);
mutex_unlock(&v9inode->v_mutex);
goto out_error;
}
v9inode->writeback_fid = (void *) fid;
}
mutex_unlock(&v9inode->v_mutex);
#ifdef CONFIG_9P_FSCACHE
if (v9ses->cache)
v9fs_cache_inode_set_cookie(inode, file);
#endif
return 0;
out_error:
p9_client_clunk(file->private_data);
file->private_data = NULL;
return err;
}
/**
* v9fs_file_lock - lock a file (or directory)
* @filp: file to be locked
* @cmd: lock command
* @fl: file lock structure
*
* Bugs: this looks like a local only lock, we should extend into 9P
* by using open exclusive
*/
static int v9fs_file_lock(struct file *filp, int cmd, struct file_lock *fl)
{
int res = 0;
struct inode *inode = file_inode(filp);
p9_debug(P9_DEBUG_VFS, "filp: %p lock: %p\n", filp, fl);
/* No mandatory locks */
if (__mandatory_lock(inode) && fl->fl_type != F_UNLCK)
return -ENOLCK;
if ((IS_SETLK(cmd) || IS_SETLKW(cmd)) && fl->fl_type != F_UNLCK) {
filemap_write_and_wait(inode->i_mapping);
invalidate_mapping_pages(&inode->i_data, 0, -1);
}
return res;
}
static int v9fs_file_do_lock(struct file *filp, int cmd, struct file_lock *fl)
{
struct p9_flock flock;
struct p9_fid *fid;
uint8_t status;
int res = 0;
unsigned char fl_type;
fid = filp->private_data;
BUG_ON(fid == NULL);
if ((fl->fl_flags & FL_POSIX) != FL_POSIX)
BUG();
res = posix_lock_file_wait(filp, fl);
if (res < 0)
goto out;
/* convert posix lock to p9 tlock args */
memset(&flock, 0, sizeof(flock));
/* map the lock type */
switch (fl->fl_type) {
case F_RDLCK:
flock.type = P9_LOCK_TYPE_RDLCK;
break;
case F_WRLCK:
flock.type = P9_LOCK_TYPE_WRLCK;
break;
case F_UNLCK:
flock.type = P9_LOCK_TYPE_UNLCK;
break;
}
flock.start = fl->fl_start;
if (fl->fl_end == OFFSET_MAX)
flock.length = 0;
else
flock.length = fl->fl_end - fl->fl_start + 1;
flock.proc_id = fl->fl_pid;
flock.client_id = utsname()->nodename;
if (IS_SETLKW(cmd))
flock.flags = P9_LOCK_FLAGS_BLOCK;
/*
* if its a blocked request and we get P9_LOCK_BLOCKED as the status
* for lock request, keep on trying
*/
for (;;) {
res = p9_client_lock_dotl(fid, &flock, &status);
if (res < 0)
break;
if (status != P9_LOCK_BLOCKED)
break;
if (status == P9_LOCK_BLOCKED && !IS_SETLKW(cmd))
break;
if (schedule_timeout_interruptible(P9_LOCK_TIMEOUT) != 0)
break;
}
/* map 9p status to VFS status */
switch (status) {
case P9_LOCK_SUCCESS:
res = 0;
break;
case P9_LOCK_BLOCKED:
res = -EAGAIN;
break;
case P9_LOCK_ERROR:
case P9_LOCK_GRACE:
res = -ENOLCK;
break;
default:
BUG();
}
/*
* incase server returned error for lock request, revert
* it locally
*/
if (res < 0 && fl->fl_type != F_UNLCK) {
fl_type = fl->fl_type;
fl->fl_type = F_UNLCK;
res = posix_lock_file_wait(filp, fl);
fl->fl_type = fl_type;
}
out:
return res;
}
static int v9fs_file_getlock(struct file *filp, struct file_lock *fl)
{
struct p9_getlock glock;
struct p9_fid *fid;
int res = 0;
fid = filp->private_data;
BUG_ON(fid == NULL);
posix_test_lock(filp, fl);
/*
* if we have a conflicting lock locally, no need to validate
* with server
*/
if (fl->fl_type != F_UNLCK)
return res;
/* convert posix lock to p9 tgetlock args */
memset(&glock, 0, sizeof(glock));
glock.type = P9_LOCK_TYPE_UNLCK;
glock.start = fl->fl_start;
if (fl->fl_end == OFFSET_MAX)
glock.length = 0;
else
glock.length = fl->fl_end - fl->fl_start + 1;
glock.proc_id = fl->fl_pid;
glock.client_id = utsname()->nodename;
res = p9_client_getlock_dotl(fid, &glock);
if (res < 0)
return res;
/* map 9p lock type to os lock type */
switch (glock.type) {
case P9_LOCK_TYPE_RDLCK:
fl->fl_type = F_RDLCK;
break;
case P9_LOCK_TYPE_WRLCK:
fl->fl_type = F_WRLCK;
break;
case P9_LOCK_TYPE_UNLCK:
fl->fl_type = F_UNLCK;
break;
}
if (glock.type != P9_LOCK_TYPE_UNLCK) {
fl->fl_start = glock.start;
if (glock.length == 0)
fl->fl_end = OFFSET_MAX;
else
fl->fl_end = glock.start + glock.length - 1;
fl->fl_pid = glock.proc_id;
}
return res;
}
/**
* v9fs_file_lock_dotl - lock a file (or directory)
* @filp: file to be locked
* @cmd: lock command
* @fl: file lock structure
*
*/
static int v9fs_file_lock_dotl(struct file *filp, int cmd, struct file_lock *fl)
{
struct inode *inode = file_inode(filp);
int ret = -ENOLCK;
p9_debug(P9_DEBUG_VFS, "filp: %p cmd:%d lock: %p name: %s\n",
filp, cmd, fl, filp->f_path.dentry->d_name.name);
/* No mandatory locks */
if (__mandatory_lock(inode) && fl->fl_type != F_UNLCK)
goto out_err;
if ((IS_SETLK(cmd) || IS_SETLKW(cmd)) && fl->fl_type != F_UNLCK) {
filemap_write_and_wait(inode->i_mapping);
invalidate_mapping_pages(&inode->i_data, 0, -1);
}
if (IS_SETLK(cmd) || IS_SETLKW(cmd))
ret = v9fs_file_do_lock(filp, cmd, fl);
else if (IS_GETLK(cmd))
ret = v9fs_file_getlock(filp, fl);
else
ret = -EINVAL;
out_err:
return ret;
}
/**
* v9fs_file_flock_dotl - lock a file
* @filp: file to be locked
* @cmd: lock command
* @fl: file lock structure
*
*/
static int v9fs_file_flock_dotl(struct file *filp, int cmd,
struct file_lock *fl)
{
struct inode *inode = file_inode(filp);
int ret = -ENOLCK;
p9_debug(P9_DEBUG_VFS, "filp: %p cmd:%d lock: %p name: %s\n",
filp, cmd, fl, filp->f_path.dentry->d_name.name);
/* No mandatory locks */
if (__mandatory_lock(inode) && fl->fl_type != F_UNLCK)
goto out_err;
if (!(fl->fl_flags & FL_FLOCK))
goto out_err;
if ((IS_SETLK(cmd) || IS_SETLKW(cmd)) && fl->fl_type != F_UNLCK) {
filemap_write_and_wait(inode->i_mapping);
invalidate_mapping_pages(&inode->i_data, 0, -1);
}
/* Convert flock to posix lock */
fl->fl_owner = (fl_owner_t)filp;
fl->fl_start = 0;
fl->fl_end = OFFSET_MAX;
fl->fl_flags |= FL_POSIX;
fl->fl_flags ^= FL_FLOCK;
if (IS_SETLK(cmd) | IS_SETLKW(cmd))
ret = v9fs_file_do_lock(filp, cmd, fl);
else
ret = -EINVAL;
out_err:
return ret;
}
/**
* v9fs_fid_readn - read from a fid
* @fid: fid to read
* @data: data buffer to read data into
* @udata: user data buffer to read data into
* @count: size of buffer
* @offset: offset at which to read data
*
*/
ssize_t
v9fs_fid_readn(struct p9_fid *fid, char *data, char __user *udata, u32 count,
u64 offset)
{
int n, total, size;
p9_debug(P9_DEBUG_VFS, "fid %d offset %llu count %d\n",
fid->fid, (long long unsigned)offset, count);
n = 0;
total = 0;
size = fid->iounit ? fid->iounit : fid->clnt->msize - P9_IOHDRSZ;
do {
n = p9_client_read(fid, data, udata, offset, count);
if (n <= 0)
break;
if (data)
data += n;
if (udata)
udata += n;
offset += n;
count -= n;
total += n;
} while (count > 0 && n == size);
if (n < 0)
total = n;
return total;
}
/**
* v9fs_file_readn - read from a file
* @filp: file pointer to read
* @data: data buffer to read data into
* @udata: user data buffer to read data into
* @count: size of buffer
* @offset: offset at which to read data
*
*/
ssize_t
v9fs_file_readn(struct file *filp, char *data, char __user *udata, u32 count,
u64 offset)
{
return v9fs_fid_readn(filp->private_data, data, udata, count, offset);
}
/**
* v9fs_file_read - read from a file
* @filp: file pointer to read
* @udata: user data buffer to read data into
* @count: size of buffer
* @offset: offset at which to read data
*
*/
static ssize_t
v9fs_file_read(struct file *filp, char __user *udata, size_t count,
loff_t * offset)
{
int ret;
struct p9_fid *fid;
size_t size;
p9_debug(P9_DEBUG_VFS, "count %zu offset %lld\n", count, *offset);
fid = filp->private_data;
size = fid->iounit ? fid->iounit : fid->clnt->msize - P9_IOHDRSZ;
if (count > size)
ret = v9fs_file_readn(filp, NULL, udata, count, *offset);
else
ret = p9_client_read(fid, NULL, udata, *offset, count);
if (ret > 0)
*offset += ret;
return ret;
}
ssize_t
v9fs_file_write_internal(struct inode *inode, struct p9_fid *fid,
const char __user *data, size_t count,
loff_t *offset, int invalidate)
{
int n;
loff_t i_size;
size_t total = 0;
struct p9_client *clnt;
loff_t origin = *offset;
unsigned long pg_start, pg_end;
p9_debug(P9_DEBUG_VFS, "data %p count %d offset %x\n",
data, (int)count, (int)*offset);
clnt = fid->clnt;
do {
n = p9_client_write(fid, NULL, data+total, origin+total, count);
if (n <= 0)
break;
count -= n;
total += n;
} while (count > 0);
if (invalidate && (total > 0)) {
pg_start = origin >> PAGE_CACHE_SHIFT;
pg_end = (origin + total - 1) >> PAGE_CACHE_SHIFT;
if (inode->i_mapping && inode->i_mapping->nrpages)
invalidate_inode_pages2_range(inode->i_mapping,
pg_start, pg_end);
*offset += total;
i_size = i_size_read(inode);
if (*offset > i_size) {
inode_add_bytes(inode, *offset - i_size);
i_size_write(inode, *offset);
}
}
if (n < 0)
return n;
return total;
}
/**
* v9fs_file_write - write to a file
* @filp: file pointer to write
* @data: data buffer to write data from
* @count: size of buffer
* @offset: offset at which to write data
*
*/
static ssize_t
v9fs_file_write(struct file *filp, const char __user * data,
size_t count, loff_t *offset)
{
ssize_t retval = 0;
loff_t origin = *offset;
retval = generic_write_checks(filp, &origin, &count, 0);
if (retval)
goto out;
retval = -EINVAL;
if ((ssize_t) count < 0)
goto out;
retval = 0;
if (!count)
goto out;
retval = v9fs_file_write_internal(file_inode(filp),
filp->private_data,
data, count, &origin, 1);
/* update offset on successful write */
if (retval > 0)
*offset = origin;
out:
return retval;
}
static int v9fs_file_fsync(struct file *filp, loff_t start, loff_t end,
int datasync)
{
struct p9_fid *fid;
struct inode *inode = filp->f_mapping->host;
struct p9_wstat wstat;
int retval;
retval = filemap_write_and_wait_range(inode->i_mapping, start, end);
if (retval)
return retval;
mutex_lock(&inode->i_mutex);
p9_debug(P9_DEBUG_VFS, "filp %p datasync %x\n", filp, datasync);
fid = filp->private_data;
v9fs_blank_wstat(&wstat);
retval = p9_client_wstat(fid, &wstat);
mutex_unlock(&inode->i_mutex);
return retval;
}
int v9fs_file_fsync_dotl(struct file *filp, loff_t start, loff_t end,
int datasync)
{
struct p9_fid *fid;
struct inode *inode = filp->f_mapping->host;
int retval;
retval = filemap_write_and_wait_range(inode->i_mapping, start, end);
if (retval)
return retval;
mutex_lock(&inode->i_mutex);
p9_debug(P9_DEBUG_VFS, "filp %p datasync %x\n", filp, datasync);
fid = filp->private_data;
retval = p9_client_fsync(fid, datasync);
mutex_unlock(&inode->i_mutex);
return retval;
}
static int
v9fs_file_mmap(struct file *file, struct vm_area_struct *vma)
{
int retval;
retval = generic_file_mmap(file, vma);
if (!retval)
vma->vm_ops = &v9fs_file_vm_ops;
return retval;
}
static int
v9fs_vm_page_mkwrite(struct vm_area_struct *vma, struct vm_fault *vmf)
{
struct v9fs_inode *v9inode;
struct page *page = vmf->page;
struct file *filp = vma->vm_file;
struct inode *inode = file_inode(filp);
p9_debug(P9_DEBUG_VFS, "page %p fid %lx\n",
page, (unsigned long)filp->private_data);
/* Update file times before taking page lock */
file_update_time(filp);
v9inode = V9FS_I(inode);
/* make sure the cache has finished storing the page */
v9fs_fscache_wait_on_page_write(inode, page);
BUG_ON(!v9inode->writeback_fid);
lock_page(page);
if (page->mapping != inode->i_mapping)
goto out_unlock;
wait_for_stable_page(page);
return VM_FAULT_LOCKED;
out_unlock:
unlock_page(page);
return VM_FAULT_NOPAGE;
}
static ssize_t
v9fs_direct_read(struct file *filp, char __user *udata, size_t count,
loff_t *offsetp)
{
loff_t size, offset;
struct inode *inode;
struct address_space *mapping;
offset = *offsetp;
mapping = filp->f_mapping;
inode = mapping->host;
if (!count)
return 0;
size = i_size_read(inode);
if (offset < size)
filemap_write_and_wait_range(mapping, offset,
offset + count - 1);
return v9fs_file_read(filp, udata, count, offsetp);
}
/**
* v9fs_cached_file_read - read from a file
* @filp: file pointer to read
* @udata: user data buffer to read data into
* @count: size of buffer
* @offset: offset at which to read data
*
*/
static ssize_t
v9fs_cached_file_read(struct file *filp, char __user *data, size_t count,
loff_t *offset)
{
if (filp->f_flags & O_DIRECT)
return v9fs_direct_read(filp, data, count, offset);
return do_sync_read(filp, data, count, offset);
}
static ssize_t
v9fs_direct_write(struct file *filp, const char __user * data,
size_t count, loff_t *offsetp)
{
loff_t offset;
ssize_t retval;
struct inode *inode;
struct address_space *mapping;
offset = *offsetp;
mapping = filp->f_mapping;
inode = mapping->host;
if (!count)
return 0;
mutex_lock(&inode->i_mutex);
retval = filemap_write_and_wait_range(mapping, offset,
offset + count - 1);
if (retval)
goto err_out;
/*
* After a write we want buffered reads to be sure to go to disk to get
* the new data. We invalidate clean cached page from the region we're
* about to write. We do this *before* the write so that if we fail
* here we fall back to buffered write
*/
if (mapping->nrpages) {
pgoff_t pg_start = offset >> PAGE_CACHE_SHIFT;
pgoff_t pg_end = (offset + count - 1) >> PAGE_CACHE_SHIFT;
retval = invalidate_inode_pages2_range(mapping,
pg_start, pg_end);
/*
* If a page can not be invalidated, fall back
* to buffered write.
*/
if (retval) {
if (retval == -EBUSY)
goto buff_write;
goto err_out;
}
}
retval = v9fs_file_write(filp, data, count, offsetp);
err_out:
mutex_unlock(&inode->i_mutex);
return retval;
buff_write:
mutex_unlock(&inode->i_mutex);
return do_sync_write(filp, data, count, offsetp);
}
/**
* v9fs_cached_file_write - write to a file
* @filp: file pointer to write
* @data: data buffer to write data from
* @count: size of buffer
* @offset: offset at which to write data
*
*/
static ssize_t
v9fs_cached_file_write(struct file *filp, const char __user * data,
size_t count, loff_t *offset)
{
if (filp->f_flags & O_DIRECT)
return v9fs_direct_write(filp, data, count, offset);
return do_sync_write(filp, data, count, offset);
}
static const struct vm_operations_struct v9fs_file_vm_ops = {
.fault = filemap_fault,
.page_mkwrite = v9fs_vm_page_mkwrite,
.remap_pages = generic_file_remap_pages,
};
const struct file_operations v9fs_cached_file_operations = {
.llseek = generic_file_llseek,
.read = v9fs_cached_file_read,
.write = v9fs_cached_file_write,
.aio_read = generic_file_aio_read,
.aio_write = generic_file_aio_write,
.open = v9fs_file_open,
.release = v9fs_dir_release,
.lock = v9fs_file_lock,
.mmap = v9fs_file_mmap,
.fsync = v9fs_file_fsync,
};
const struct file_operations v9fs_cached_file_operations_dotl = {
.llseek = generic_file_llseek,
.read = v9fs_cached_file_read,
.write = v9fs_cached_file_write,
.aio_read = generic_file_aio_read,
.aio_write = generic_file_aio_write,
.open = v9fs_file_open,
.release = v9fs_dir_release,
.lock = v9fs_file_lock_dotl,
.flock = v9fs_file_flock_dotl,
.mmap = v9fs_file_mmap,
.fsync = v9fs_file_fsync_dotl,
};
const struct file_operations v9fs_file_operations = {
.llseek = generic_file_llseek,
.read = v9fs_file_read,
.write = v9fs_file_write,
.open = v9fs_file_open,
.release = v9fs_dir_release,
.lock = v9fs_file_lock,
.mmap = generic_file_readonly_mmap,
.fsync = v9fs_file_fsync,
};
const struct file_operations v9fs_file_operations_dotl = {
.llseek = generic_file_llseek,
.read = v9fs_file_read,
.write = v9fs_file_write,
.open = v9fs_file_open,
.release = v9fs_dir_release,
.lock = v9fs_file_lock_dotl,
.flock = v9fs_file_flock_dotl,
.mmap = generic_file_readonly_mmap,
.fsync = v9fs_file_fsync_dotl,
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,368 @@
/*
* linux/fs/9p/vfs_super.c
*
* This file contians superblock ops for 9P2000. It is intended that
* you mount this file system on directories.
*
* Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
* Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/stat.h>
#include <linux/string.h>
#include <linux/inet.h>
#include <linux/pagemap.h>
#include <linux/seq_file.h>
#include <linux/mount.h>
#include <linux/idr.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/statfs.h>
#include <linux/magic.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
#include "v9fs.h"
#include "v9fs_vfs.h"
#include "fid.h"
#include "xattr.h"
#include "acl.h"
static const struct super_operations v9fs_super_ops, v9fs_super_ops_dotl;
/**
* v9fs_set_super - set the superblock
* @s: super block
* @data: file system specific data
*
*/
static int v9fs_set_super(struct super_block *s, void *data)
{
s->s_fs_info = data;
return set_anon_super(s, data);
}
/**
* v9fs_fill_super - populate superblock with info
* @sb: superblock
* @v9ses: session information
* @flags: flags propagated from v9fs_mount()
*
*/
static void
v9fs_fill_super(struct super_block *sb, struct v9fs_session_info *v9ses,
int flags, void *data)
{
sb->s_maxbytes = MAX_LFS_FILESIZE;
sb->s_blocksize_bits = fls(v9ses->maxdata - 1);
sb->s_blocksize = 1 << sb->s_blocksize_bits;
sb->s_magic = V9FS_MAGIC;
if (v9fs_proto_dotl(v9ses)) {
sb->s_op = &v9fs_super_ops_dotl;
sb->s_xattr = v9fs_xattr_handlers;
} else
sb->s_op = &v9fs_super_ops;
sb->s_bdi = &v9ses->bdi;
if (v9ses->cache)
sb->s_bdi->ra_pages = (VM_MAX_READAHEAD * 1024)/PAGE_CACHE_SIZE;
sb->s_flags |= MS_ACTIVE | MS_DIRSYNC | MS_NOATIME;
if (!v9ses->cache)
sb->s_flags |= MS_SYNCHRONOUS;
#ifdef CONFIG_9P_FS_POSIX_ACL
if ((v9ses->flags & V9FS_ACL_MASK) == V9FS_POSIX_ACL)
sb->s_flags |= MS_POSIXACL;
#endif
save_mount_options(sb, data);
}
/**
* v9fs_mount - mount a superblock
* @fs_type: file system type
* @flags: mount flags
* @dev_name: device name that was mounted
* @data: mount options
*
*/
static struct dentry *v9fs_mount(struct file_system_type *fs_type, int flags,
const char *dev_name, void *data)
{
struct super_block *sb = NULL;
struct inode *inode = NULL;
struct dentry *root = NULL;
struct v9fs_session_info *v9ses = NULL;
umode_t mode = S_IRWXUGO | S_ISVTX;
struct p9_fid *fid;
int retval = 0;
p9_debug(P9_DEBUG_VFS, "\n");
v9ses = kzalloc(sizeof(struct v9fs_session_info), GFP_KERNEL);
if (!v9ses)
return ERR_PTR(-ENOMEM);
fid = v9fs_session_init(v9ses, dev_name, data);
if (IS_ERR(fid)) {
retval = PTR_ERR(fid);
/*
* we need to call session_close to tear down some
* of the data structure setup by session_init
*/
goto close_session;
}
sb = sget(fs_type, NULL, v9fs_set_super, flags, v9ses);
if (IS_ERR(sb)) {
retval = PTR_ERR(sb);
goto clunk_fid;
}
v9fs_fill_super(sb, v9ses, flags, data);
if (v9ses->cache)
sb->s_d_op = &v9fs_cached_dentry_operations;
else
sb->s_d_op = &v9fs_dentry_operations;
inode = v9fs_get_inode(sb, S_IFDIR | mode, 0);
if (IS_ERR(inode)) {
retval = PTR_ERR(inode);
goto release_sb;
}
root = d_make_root(inode);
if (!root) {
retval = -ENOMEM;
goto release_sb;
}
sb->s_root = root;
if (v9fs_proto_dotl(v9ses)) {
struct p9_stat_dotl *st = NULL;
st = p9_client_getattr_dotl(fid, P9_STATS_BASIC);
if (IS_ERR(st)) {
retval = PTR_ERR(st);
goto release_sb;
}
root->d_inode->i_ino = v9fs_qid2ino(&st->qid);
v9fs_stat2inode_dotl(st, root->d_inode);
kfree(st);
} else {
struct p9_wstat *st = NULL;
st = p9_client_stat(fid);
if (IS_ERR(st)) {
retval = PTR_ERR(st);
goto release_sb;
}
root->d_inode->i_ino = v9fs_qid2ino(&st->qid);
v9fs_stat2inode(st, root->d_inode, sb);
p9stat_free(st);
kfree(st);
}
retval = v9fs_get_acl(inode, fid);
if (retval)
goto release_sb;
v9fs_fid_add(root, fid);
p9_debug(P9_DEBUG_VFS, " simple set mount, return 0\n");
return dget(sb->s_root);
clunk_fid:
p9_client_clunk(fid);
close_session:
v9fs_session_close(v9ses);
kfree(v9ses);
return ERR_PTR(retval);
release_sb:
/*
* we will do the session_close and root dentry release
* in the below call. But we need to clunk fid, because we haven't
* attached the fid to dentry so it won't get clunked
* automatically.
*/
p9_client_clunk(fid);
deactivate_locked_super(sb);
return ERR_PTR(retval);
}
/**
* v9fs_kill_super - Kill Superblock
* @s: superblock
*
*/
static void v9fs_kill_super(struct super_block *s)
{
struct v9fs_session_info *v9ses = s->s_fs_info;
p9_debug(P9_DEBUG_VFS, " %p\n", s);
kill_anon_super(s);
v9fs_session_cancel(v9ses);
v9fs_session_close(v9ses);
kfree(v9ses);
s->s_fs_info = NULL;
p9_debug(P9_DEBUG_VFS, "exiting kill_super\n");
}
static void
v9fs_umount_begin(struct super_block *sb)
{
struct v9fs_session_info *v9ses;
v9ses = sb->s_fs_info;
v9fs_session_begin_cancel(v9ses);
}
static int v9fs_statfs(struct dentry *dentry, struct kstatfs *buf)
{
struct v9fs_session_info *v9ses;
struct p9_fid *fid;
struct p9_rstatfs rs;
int res;
fid = v9fs_fid_lookup(dentry);
if (IS_ERR(fid)) {
res = PTR_ERR(fid);
goto done;
}
v9ses = v9fs_dentry2v9ses(dentry);
if (v9fs_proto_dotl(v9ses)) {
res = p9_client_statfs(fid, &rs);
if (res == 0) {
buf->f_type = rs.type;
buf->f_bsize = rs.bsize;
buf->f_blocks = rs.blocks;
buf->f_bfree = rs.bfree;
buf->f_bavail = rs.bavail;
buf->f_files = rs.files;
buf->f_ffree = rs.ffree;
buf->f_fsid.val[0] = rs.fsid & 0xFFFFFFFFUL;
buf->f_fsid.val[1] = (rs.fsid >> 32) & 0xFFFFFFFFUL;
buf->f_namelen = rs.namelen;
}
if (res != -ENOSYS)
goto done;
}
res = simple_statfs(dentry, buf);
done:
return res;
}
static int v9fs_drop_inode(struct inode *inode)
{
struct v9fs_session_info *v9ses;
v9ses = v9fs_inode2v9ses(inode);
if (v9ses->cache)
return generic_drop_inode(inode);
/*
* in case of non cached mode always drop the
* the inode because we want the inode attribute
* to always match that on the server.
*/
return 1;
}
static int v9fs_write_inode(struct inode *inode,
struct writeback_control *wbc)
{
int ret;
struct p9_wstat wstat;
struct v9fs_inode *v9inode;
/*
* send an fsync request to server irrespective of
* wbc->sync_mode.
*/
p9_debug(P9_DEBUG_VFS, "%s: inode %p\n", __func__, inode);
v9inode = V9FS_I(inode);
if (!v9inode->writeback_fid)
return 0;
v9fs_blank_wstat(&wstat);
ret = p9_client_wstat(v9inode->writeback_fid, &wstat);
if (ret < 0) {
__mark_inode_dirty(inode, I_DIRTY_DATASYNC);
return ret;
}
return 0;
}
static int v9fs_write_inode_dotl(struct inode *inode,
struct writeback_control *wbc)
{
int ret;
struct v9fs_inode *v9inode;
/*
* send an fsync request to server irrespective of
* wbc->sync_mode.
*/
p9_debug(P9_DEBUG_VFS, "%s: inode %p\n", __func__, inode);
v9inode = V9FS_I(inode);
if (!v9inode->writeback_fid)
return 0;
ret = p9_client_fsync(v9inode->writeback_fid, 0);
if (ret < 0) {
__mark_inode_dirty(inode, I_DIRTY_DATASYNC);
return ret;
}
return 0;
}
static const struct super_operations v9fs_super_ops = {
.alloc_inode = v9fs_alloc_inode,
.destroy_inode = v9fs_destroy_inode,
.statfs = simple_statfs,
.evict_inode = v9fs_evict_inode,
.show_options = generic_show_options,
.umount_begin = v9fs_umount_begin,
.write_inode = v9fs_write_inode,
};
static const struct super_operations v9fs_super_ops_dotl = {
.alloc_inode = v9fs_alloc_inode,
.destroy_inode = v9fs_destroy_inode,
.statfs = v9fs_statfs,
.drop_inode = v9fs_drop_inode,
.evict_inode = v9fs_evict_inode,
.show_options = generic_show_options,
.umount_begin = v9fs_umount_begin,
.write_inode = v9fs_write_inode_dotl,
};
struct file_system_type v9fs_fs_type = {
.name = "9p",
.mount = v9fs_mount,
.kill_sb = v9fs_kill_super,
.owner = THIS_MODULE,
.fs_flags = FS_RENAME_DOES_D_MOVE,
};
MODULE_ALIAS_FS("9p");

View File

@@ -0,0 +1,175 @@
/*
* Copyright IBM Corporation, 2010
* Author Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2.1 of the GNU Lesser General Public License
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it would be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
#include "fid.h"
#include "xattr.h"
ssize_t v9fs_fid_xattr_get(struct p9_fid *fid, const char *name,
void *buffer, size_t buffer_size)
{
ssize_t retval;
int msize, read_count;
u64 offset = 0, attr_size;
struct p9_fid *attr_fid;
attr_fid = p9_client_xattrwalk(fid, name, &attr_size);
if (IS_ERR(attr_fid)) {
retval = PTR_ERR(attr_fid);
p9_debug(P9_DEBUG_VFS, "p9_client_attrwalk failed %zd\n",
retval);
attr_fid = NULL;
goto error;
}
if (!buffer_size) {
/* request to get the attr_size */
retval = attr_size;
goto error;
}
if (attr_size > buffer_size) {
retval = -ERANGE;
goto error;
}
msize = attr_fid->clnt->msize;
while (attr_size) {
if (attr_size > (msize - P9_IOHDRSZ))
read_count = msize - P9_IOHDRSZ;
else
read_count = attr_size;
read_count = p9_client_read(attr_fid, ((char *)buffer)+offset,
NULL, offset, read_count);
if (read_count < 0) {
/* error in xattr read */
retval = read_count;
goto error;
}
offset += read_count;
attr_size -= read_count;
}
/* Total read xattr bytes */
retval = offset;
error:
if (attr_fid)
p9_client_clunk(attr_fid);
return retval;
}
/*
* v9fs_xattr_get()
*
* Copy an extended attribute into the buffer
* provided, or compute the buffer size required.
* Buffer is NULL to compute the size of the buffer required.
*
* Returns a negative error number on failure, or the number of bytes
* used / required on success.
*/
ssize_t v9fs_xattr_get(struct dentry *dentry, const char *name,
void *buffer, size_t buffer_size)
{
struct p9_fid *fid;
p9_debug(P9_DEBUG_VFS, "name = %s value_len = %zu\n",
name, buffer_size);
fid = v9fs_fid_lookup(dentry);
if (IS_ERR(fid))
return PTR_ERR(fid);
return v9fs_fid_xattr_get(fid, name, buffer, buffer_size);
}
/*
* v9fs_xattr_set()
*
* Create, replace or remove an extended attribute for this inode. Buffer
* is NULL to remove an existing extended attribute, and non-NULL to
* either replace an existing extended attribute, or create a new extended
* attribute. The flags XATTR_REPLACE and XATTR_CREATE
* specify that an extended attribute must exist and must not exist
* previous to the call, respectively.
*
* Returns 0, or a negative error number on failure.
*/
int v9fs_xattr_set(struct dentry *dentry, const char *name,
const void *value, size_t value_len, int flags)
{
struct p9_fid *fid = v9fs_fid_lookup(dentry);
if (IS_ERR(fid))
return PTR_ERR(fid);
return v9fs_fid_xattr_set(fid, name, value, value_len, flags);
}
int v9fs_fid_xattr_set(struct p9_fid *fid, const char *name,
const void *value, size_t value_len, int flags)
{
u64 offset = 0;
int retval, msize, write_count;
p9_debug(P9_DEBUG_VFS, "name = %s value_len = %zu flags = %d\n",
name, value_len, flags);
/* Clone it */
fid = p9_client_walk(fid, 0, NULL, 1);
if (IS_ERR(fid))
return PTR_ERR(fid);
/*
* On success fid points to xattr
*/
retval = p9_client_xattrcreate(fid, name, value_len, flags);
if (retval < 0) {
p9_debug(P9_DEBUG_VFS, "p9_client_xattrcreate failed %d\n",
retval);
p9_client_clunk(fid);
return retval;
}
msize = fid->clnt->msize;
while (value_len) {
if (value_len > (msize - P9_IOHDRSZ))
write_count = msize - P9_IOHDRSZ;
else
write_count = value_len;
write_count = p9_client_write(fid, ((char *)value)+offset,
NULL, offset, write_count);
if (write_count < 0) {
/* error in xattr write */
retval = write_count;
break;
}
offset += write_count;
value_len -= write_count;
}
return p9_client_clunk(fid);
}
ssize_t v9fs_listxattr(struct dentry *dentry, char *buffer, size_t buffer_size)
{
return v9fs_xattr_get(dentry, NULL, buffer, buffer_size);
}
const struct xattr_handler *v9fs_xattr_handlers[] = {
&v9fs_xattr_user_handler,
#ifdef CONFIG_9P_FS_POSIX_ACL
&v9fs_xattr_acl_access_handler,
&v9fs_xattr_acl_default_handler,
#endif
NULL
};

View File

@@ -0,0 +1,35 @@
/*
* Copyright IBM Corporation, 2010
* Author Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2.1 of the GNU Lesser General Public License
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it would be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
*/
#ifndef FS_9P_XATTR_H
#define FS_9P_XATTR_H
#include <linux/xattr.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
extern const struct xattr_handler *v9fs_xattr_handlers[];
extern struct xattr_handler v9fs_xattr_user_handler;
extern const struct xattr_handler v9fs_xattr_acl_access_handler;
extern const struct xattr_handler v9fs_xattr_acl_default_handler;
extern ssize_t v9fs_fid_xattr_get(struct p9_fid *, const char *,
void *, size_t);
extern ssize_t v9fs_xattr_get(struct dentry *, const char *,
void *, size_t);
extern int v9fs_fid_xattr_set(struct p9_fid *, const char *,
const void *, size_t, int);
extern int v9fs_xattr_set(struct dentry *, const char *,
const void *, size_t, int);
extern ssize_t v9fs_listxattr(struct dentry *, char *, size_t);
#endif /* FS_9P_XATTR_H */

View File

@@ -0,0 +1,80 @@
/*
* Copyright IBM Corporation, 2010
* Author Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2.1 of the GNU Lesser General Public License
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it would be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
*/
#include <linux/module.h>
#include <linux/string.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include "xattr.h"
static int v9fs_xattr_user_get(struct dentry *dentry, const char *name,
void *buffer, size_t size, int type)
{
int retval;
char *full_name;
size_t name_len;
size_t prefix_len = XATTR_USER_PREFIX_LEN;
if (name == NULL)
return -EINVAL;
if (strcmp(name, "") == 0)
return -EINVAL;
name_len = strlen(name);
full_name = kmalloc(prefix_len + name_len + 1 , GFP_KERNEL);
if (!full_name)
return -ENOMEM;
memcpy(full_name, XATTR_USER_PREFIX, prefix_len);
memcpy(full_name+prefix_len, name, name_len);
full_name[prefix_len + name_len] = '\0';
retval = v9fs_xattr_get(dentry, full_name, buffer, size);
kfree(full_name);
return retval;
}
static int v9fs_xattr_user_set(struct dentry *dentry, const char *name,
const void *value, size_t size, int flags, int type)
{
int retval;
char *full_name;
size_t name_len;
size_t prefix_len = XATTR_USER_PREFIX_LEN;
if (name == NULL)
return -EINVAL;
if (strcmp(name, "") == 0)
return -EINVAL;
name_len = strlen(name);
full_name = kmalloc(prefix_len + name_len + 1 , GFP_KERNEL);
if (!full_name)
return -ENOMEM;
memcpy(full_name, XATTR_USER_PREFIX, prefix_len);
memcpy(full_name + prefix_len, name, name_len);
full_name[prefix_len + name_len] = '\0';
retval = v9fs_xattr_set(dentry, full_name, value, size, flags);
kfree(full_name);
return retval;
}
struct xattr_handler v9fs_xattr_user_handler = {
.prefix = XATTR_USER_PREFIX,
.get = v9fs_xattr_user_get,
.set = v9fs_xattr_user_set,
};

View File

@@ -0,0 +1,35 @@
obj-m := 9p.o
9p-objs := \
vfs_super.o \
vfs_inode.o \
vfs_inode_dotl.o \
vfs_addr.o \
vfs_file.o \
vfs_dir.o \
vfs_dentry.o \
v9fs.o \
fid.o \
xattr.o
9p-y += cache.o
9p-n += acl.o
obj-m := 9pnet.o
obj-m += 9pnet_virtio.o
obj-n += 9pnet_rdma.o
9pnet-objs := \
mod.o \
client.o \
error.o \
util.o \
protocol.o \
trans_fd.o \
trans_common.o \
9pnet_virtio-objs := \
trans_virtio.o
9pnet_rdma-objs := \
trans_rdma.o

338
addons/9p/src/4.4.180/acl.c Normal file
View File

@@ -0,0 +1,338 @@
/*
* Copyright IBM Corporation, 2010
* Author Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2.1 of the GNU Lesser General Public License
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it would be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/posix_acl_xattr.h>
#include "xattr.h"
#include "acl.h"
#include "v9fs.h"
#include "v9fs_vfs.h"
#include "fid.h"
static struct posix_acl *__v9fs_get_acl(struct p9_fid *fid, char *name)
{
ssize_t size;
void *value = NULL;
struct posix_acl *acl = NULL;
size = v9fs_fid_xattr_get(fid, name, NULL, 0);
if (size > 0) {
value = kzalloc(size, GFP_NOFS);
if (!value)
return ERR_PTR(-ENOMEM);
size = v9fs_fid_xattr_get(fid, name, value, size);
if (size > 0) {
acl = posix_acl_from_xattr(&init_user_ns, value, size);
if (IS_ERR(acl))
goto err_out;
}
} else if (size == -ENODATA || size == 0 ||
size == -ENOSYS || size == -EOPNOTSUPP) {
acl = NULL;
} else
acl = ERR_PTR(-EIO);
err_out:
kfree(value);
return acl;
}
int v9fs_get_acl(struct inode *inode, struct p9_fid *fid)
{
int retval = 0;
struct posix_acl *pacl, *dacl;
struct v9fs_session_info *v9ses;
v9ses = v9fs_inode2v9ses(inode);
if (((v9ses->flags & V9FS_ACCESS_MASK) != V9FS_ACCESS_CLIENT) ||
((v9ses->flags & V9FS_ACL_MASK) != V9FS_POSIX_ACL)) {
set_cached_acl(inode, ACL_TYPE_DEFAULT, NULL);
set_cached_acl(inode, ACL_TYPE_ACCESS, NULL);
return 0;
}
/* get the default/access acl values and cache them */
dacl = __v9fs_get_acl(fid, POSIX_ACL_XATTR_DEFAULT);
pacl = __v9fs_get_acl(fid, POSIX_ACL_XATTR_ACCESS);
if (!IS_ERR(dacl) && !IS_ERR(pacl)) {
set_cached_acl(inode, ACL_TYPE_DEFAULT, dacl);
set_cached_acl(inode, ACL_TYPE_ACCESS, pacl);
} else
retval = -EIO;
if (!IS_ERR(dacl))
posix_acl_release(dacl);
if (!IS_ERR(pacl))
posix_acl_release(pacl);
return retval;
}
static struct posix_acl *v9fs_get_cached_acl(struct inode *inode, int type)
{
struct posix_acl *acl;
/*
* 9p Always cache the acl value when
* instantiating the inode (v9fs_inode_from_fid)
*/
acl = get_cached_acl(inode, type);
BUG_ON(acl == ACL_NOT_CACHED);
return acl;
}
struct posix_acl *v9fs_iop_get_acl(struct inode *inode, int type)
{
struct v9fs_session_info *v9ses;
v9ses = v9fs_inode2v9ses(inode);
if (((v9ses->flags & V9FS_ACCESS_MASK) != V9FS_ACCESS_CLIENT) ||
((v9ses->flags & V9FS_ACL_MASK) != V9FS_POSIX_ACL)) {
/*
* On access = client and acl = on mode get the acl
* values from the server
*/
return NULL;
}
return v9fs_get_cached_acl(inode, type);
}
static int v9fs_set_acl(struct p9_fid *fid, int type, struct posix_acl *acl)
{
int retval;
char *name;
size_t size;
void *buffer;
if (!acl)
return 0;
/* Set a setxattr request to server */
size = posix_acl_xattr_size(acl->a_count);
buffer = kmalloc(size, GFP_KERNEL);
if (!buffer)
return -ENOMEM;
retval = posix_acl_to_xattr(&init_user_ns, acl, buffer, size);
if (retval < 0)
goto err_free_out;
switch (type) {
case ACL_TYPE_ACCESS:
name = POSIX_ACL_XATTR_ACCESS;
break;
case ACL_TYPE_DEFAULT:
name = POSIX_ACL_XATTR_DEFAULT;
break;
default:
BUG();
}
retval = v9fs_fid_xattr_set(fid, name, buffer, size, 0);
err_free_out:
kfree(buffer);
return retval;
}
int v9fs_acl_chmod(struct inode *inode, struct p9_fid *fid)
{
int retval = 0;
struct posix_acl *acl;
if (S_ISLNK(inode->i_mode))
return -EOPNOTSUPP;
acl = v9fs_get_cached_acl(inode, ACL_TYPE_ACCESS);
if (acl) {
retval = __posix_acl_chmod(&acl, GFP_KERNEL, inode->i_mode);
if (retval)
return retval;
set_cached_acl(inode, ACL_TYPE_ACCESS, acl);
retval = v9fs_set_acl(fid, ACL_TYPE_ACCESS, acl);
posix_acl_release(acl);
}
return retval;
}
int v9fs_set_create_acl(struct inode *inode, struct p9_fid *fid,
struct posix_acl *dacl, struct posix_acl *acl)
{
set_cached_acl(inode, ACL_TYPE_DEFAULT, dacl);
set_cached_acl(inode, ACL_TYPE_ACCESS, acl);
v9fs_set_acl(fid, ACL_TYPE_DEFAULT, dacl);
v9fs_set_acl(fid, ACL_TYPE_ACCESS, acl);
return 0;
}
void v9fs_put_acl(struct posix_acl *dacl,
struct posix_acl *acl)
{
posix_acl_release(dacl);
posix_acl_release(acl);
}
int v9fs_acl_mode(struct inode *dir, umode_t *modep,
struct posix_acl **dpacl, struct posix_acl **pacl)
{
int retval = 0;
umode_t mode = *modep;
struct posix_acl *acl = NULL;
if (!S_ISLNK(mode)) {
acl = v9fs_get_cached_acl(dir, ACL_TYPE_DEFAULT);
if (IS_ERR(acl))
return PTR_ERR(acl);
if (!acl)
mode &= ~current_umask();
}
if (acl) {
if (S_ISDIR(mode))
*dpacl = posix_acl_dup(acl);
retval = __posix_acl_create(&acl, GFP_NOFS, &mode);
if (retval < 0)
return retval;
if (retval > 0)
*pacl = acl;
else
posix_acl_release(acl);
}
*modep = mode;
return 0;
}
static int v9fs_xattr_get_acl(const struct xattr_handler *handler,
struct dentry *dentry, const char *name,
void *buffer, size_t size)
{
struct v9fs_session_info *v9ses;
struct posix_acl *acl;
int error;
if (strcmp(name, "") != 0)
return -EINVAL;
v9ses = v9fs_dentry2v9ses(dentry);
/*
* We allow set/get/list of acl when access=client is not specified
*/
if ((v9ses->flags & V9FS_ACCESS_MASK) != V9FS_ACCESS_CLIENT)
return v9fs_xattr_get(dentry, handler->prefix, buffer, size);
acl = v9fs_get_cached_acl(d_inode(dentry), handler->flags);
if (IS_ERR(acl))
return PTR_ERR(acl);
if (acl == NULL)
return -ENODATA;
error = posix_acl_to_xattr(&init_user_ns, acl, buffer, size);
posix_acl_release(acl);
return error;
}
static int v9fs_xattr_set_acl(const struct xattr_handler *handler,
struct dentry *dentry, const char *name,
const void *value, size_t size, int flags)
{
int retval;
struct posix_acl *acl;
struct v9fs_session_info *v9ses;
struct inode *inode = d_inode(dentry);
if (strcmp(name, "") != 0)
return -EINVAL;
v9ses = v9fs_dentry2v9ses(dentry);
/*
* set the attribute on the remote. Without even looking at the
* xattr value. We leave it to the server to validate
*/
if ((v9ses->flags & V9FS_ACCESS_MASK) != V9FS_ACCESS_CLIENT)
return v9fs_xattr_set(dentry, handler->prefix, value, size,
flags);
if (S_ISLNK(inode->i_mode))
return -EOPNOTSUPP;
if (!inode_owner_or_capable(inode))
return -EPERM;
if (value) {
/* update the cached acl value */
acl = posix_acl_from_xattr(&init_user_ns, value, size);
if (IS_ERR(acl))
return PTR_ERR(acl);
else if (acl) {
retval = posix_acl_valid(acl);
if (retval)
goto err_out;
}
} else
acl = NULL;
switch (handler->flags) {
case ACL_TYPE_ACCESS:
if (acl) {
struct iattr iattr;
struct posix_acl *old_acl = acl;
retval = posix_acl_update_mode(inode, &iattr.ia_mode, &acl);
if (retval)
goto err_out;
if (!acl) {
/*
* ACL can be represented
* by the mode bits. So don't
* update ACL.
*/
posix_acl_release(old_acl);
value = NULL;
size = 0;
}
iattr.ia_valid = ATTR_MODE;
/* FIXME should we update ctime ?
* What is the following setxattr update the
* mode ?
*/
v9fs_vfs_setattr_dotl(dentry, &iattr);
}
break;
case ACL_TYPE_DEFAULT:
if (!S_ISDIR(inode->i_mode)) {
retval = acl ? -EINVAL : 0;
goto err_out;
}
break;
default:
BUG();
}
retval = v9fs_xattr_set(dentry, handler->prefix, value, size, flags);
if (!retval)
set_cached_acl(inode, handler->flags, acl);
err_out:
posix_acl_release(acl);
return retval;
}
const struct xattr_handler v9fs_xattr_acl_access_handler = {
.prefix = POSIX_ACL_XATTR_ACCESS,
.flags = ACL_TYPE_ACCESS,
.get = v9fs_xattr_get_acl,
.set = v9fs_xattr_set_acl,
};
const struct xattr_handler v9fs_xattr_acl_default_handler = {
.prefix = POSIX_ACL_XATTR_DEFAULT,
.flags = ACL_TYPE_DEFAULT,
.get = v9fs_xattr_get_acl,
.set = v9fs_xattr_set_acl,
};

View File

@@ -0,0 +1,55 @@
/*
* Copyright IBM Corporation, 2010
* Author Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2.1 of the GNU Lesser General Public License
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it would be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
*/
#ifndef FS_9P_ACL_H
#define FS_9P_ACL_H
#ifdef CONFIG_9P_FS_POSIX_ACL
extern int v9fs_get_acl(struct inode *, struct p9_fid *);
extern struct posix_acl *v9fs_iop_get_acl(struct inode *inode, int type);
extern int v9fs_acl_chmod(struct inode *, struct p9_fid *);
extern int v9fs_set_create_acl(struct inode *, struct p9_fid *,
struct posix_acl *, struct posix_acl *);
extern int v9fs_acl_mode(struct inode *dir, umode_t *modep,
struct posix_acl **dpacl, struct posix_acl **pacl);
extern void v9fs_put_acl(struct posix_acl *dacl, struct posix_acl *acl);
#else
#define v9fs_iop_get_acl NULL
static inline int v9fs_get_acl(struct inode *inode, struct p9_fid *fid)
{
return 0;
}
static inline int v9fs_acl_chmod(struct inode *inode, struct p9_fid *fid)
{
return 0;
}
static inline int v9fs_set_create_acl(struct inode *inode,
struct p9_fid *fid,
struct posix_acl *dacl,
struct posix_acl *acl)
{
return 0;
}
static inline void v9fs_put_acl(struct posix_acl *dacl,
struct posix_acl *acl)
{
}
static inline int v9fs_acl_mode(struct inode *dir, umode_t *modep,
struct posix_acl **dpacl,
struct posix_acl **pacl)
{
return 0;
}
#endif
#endif /* FS_9P_XATTR_H */

View File

@@ -0,0 +1,414 @@
/*
* V9FS cache definitions.
*
* Copyright (C) 2009 by Abhishek Kulkarni <adkulkar@umail.iu.edu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#include <linux/jiffies.h>
#include <linux/file.h>
#include <linux/slab.h>
#include <linux/stat.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <net/9p/9p.h>
#include "v9fs.h"
#include "cache.h"
#define CACHETAG_LEN 11
struct fscache_netfs v9fs_cache_netfs = {
.name = "9p",
.version = 0,
};
/**
* v9fs_random_cachetag - Generate a random tag to be associated
* with a new cache session.
*
* The value of jiffies is used for a fairly randomly cache tag.
*/
static
int v9fs_random_cachetag(struct v9fs_session_info *v9ses)
{
v9ses->cachetag = kmalloc(CACHETAG_LEN, GFP_KERNEL);
if (!v9ses->cachetag)
return -ENOMEM;
return scnprintf(v9ses->cachetag, CACHETAG_LEN, "%lu", jiffies);
}
static uint16_t v9fs_cache_session_get_key(const void *cookie_netfs_data,
void *buffer, uint16_t bufmax)
{
struct v9fs_session_info *v9ses;
uint16_t klen = 0;
v9ses = (struct v9fs_session_info *)cookie_netfs_data;
p9_debug(P9_DEBUG_FSC, "session %p buf %p size %u\n",
v9ses, buffer, bufmax);
if (v9ses->cachetag)
klen = strlen(v9ses->cachetag);
if (klen > bufmax)
return 0;
memcpy(buffer, v9ses->cachetag, klen);
p9_debug(P9_DEBUG_FSC, "cache session tag %s\n", v9ses->cachetag);
return klen;
}
const struct fscache_cookie_def v9fs_cache_session_index_def = {
.name = "9P.session",
.type = FSCACHE_COOKIE_TYPE_INDEX,
.get_key = v9fs_cache_session_get_key,
};
void v9fs_cache_session_get_cookie(struct v9fs_session_info *v9ses)
{
/* If no cache session tag was specified, we generate a random one. */
if (!v9ses->cachetag)
v9fs_random_cachetag(v9ses);
v9ses->fscache = fscache_acquire_cookie(v9fs_cache_netfs.primary_index,
&v9fs_cache_session_index_def,
v9ses, true);
p9_debug(P9_DEBUG_FSC, "session %p get cookie %p\n",
v9ses, v9ses->fscache);
}
void v9fs_cache_session_put_cookie(struct v9fs_session_info *v9ses)
{
p9_debug(P9_DEBUG_FSC, "session %p put cookie %p\n",
v9ses, v9ses->fscache);
fscache_relinquish_cookie(v9ses->fscache, 0);
v9ses->fscache = NULL;
}
static uint16_t v9fs_cache_inode_get_key(const void *cookie_netfs_data,
void *buffer, uint16_t bufmax)
{
const struct v9fs_inode *v9inode = cookie_netfs_data;
memcpy(buffer, &v9inode->qid.path, sizeof(v9inode->qid.path));
p9_debug(P9_DEBUG_FSC, "inode %p get key %llu\n",
&v9inode->vfs_inode, v9inode->qid.path);
return sizeof(v9inode->qid.path);
}
static void v9fs_cache_inode_get_attr(const void *cookie_netfs_data,
uint64_t *size)
{
const struct v9fs_inode *v9inode = cookie_netfs_data;
*size = i_size_read(&v9inode->vfs_inode);
p9_debug(P9_DEBUG_FSC, "inode %p get attr %llu\n",
&v9inode->vfs_inode, *size);
}
static uint16_t v9fs_cache_inode_get_aux(const void *cookie_netfs_data,
void *buffer, uint16_t buflen)
{
const struct v9fs_inode *v9inode = cookie_netfs_data;
memcpy(buffer, &v9inode->qid.version, sizeof(v9inode->qid.version));
p9_debug(P9_DEBUG_FSC, "inode %p get aux %u\n",
&v9inode->vfs_inode, v9inode->qid.version);
return sizeof(v9inode->qid.version);
}
static enum
fscache_checkaux v9fs_cache_inode_check_aux(void *cookie_netfs_data,
const void *buffer,
uint16_t buflen)
{
const struct v9fs_inode *v9inode = cookie_netfs_data;
if (buflen != sizeof(v9inode->qid.version))
return FSCACHE_CHECKAUX_OBSOLETE;
if (memcmp(buffer, &v9inode->qid.version,
sizeof(v9inode->qid.version)))
return FSCACHE_CHECKAUX_OBSOLETE;
return FSCACHE_CHECKAUX_OKAY;
}
static void v9fs_cache_inode_now_uncached(void *cookie_netfs_data)
{
struct v9fs_inode *v9inode = cookie_netfs_data;
struct pagevec pvec;
pgoff_t first;
int loop, nr_pages;
pagevec_init(&pvec, 0);
first = 0;
for (;;) {
nr_pages = pagevec_lookup(&pvec, v9inode->vfs_inode.i_mapping,
first,
PAGEVEC_SIZE - pagevec_count(&pvec));
if (!nr_pages)
break;
for (loop = 0; loop < nr_pages; loop++)
ClearPageFsCache(pvec.pages[loop]);
first = pvec.pages[nr_pages - 1]->index + 1;
pvec.nr = nr_pages;
pagevec_release(&pvec);
cond_resched();
}
}
const struct fscache_cookie_def v9fs_cache_inode_index_def = {
.name = "9p.inode",
.type = FSCACHE_COOKIE_TYPE_DATAFILE,
.get_key = v9fs_cache_inode_get_key,
.get_attr = v9fs_cache_inode_get_attr,
.get_aux = v9fs_cache_inode_get_aux,
.check_aux = v9fs_cache_inode_check_aux,
.now_uncached = v9fs_cache_inode_now_uncached,
};
void v9fs_cache_inode_get_cookie(struct inode *inode)
{
struct v9fs_inode *v9inode;
struct v9fs_session_info *v9ses;
if (!S_ISREG(inode->i_mode))
return;
v9inode = V9FS_I(inode);
if (v9inode->fscache)
return;
v9ses = v9fs_inode2v9ses(inode);
v9inode->fscache = fscache_acquire_cookie(v9ses->fscache,
&v9fs_cache_inode_index_def,
v9inode, true);
p9_debug(P9_DEBUG_FSC, "inode %p get cookie %p\n",
inode, v9inode->fscache);
}
void v9fs_cache_inode_put_cookie(struct inode *inode)
{
struct v9fs_inode *v9inode = V9FS_I(inode);
if (!v9inode->fscache)
return;
p9_debug(P9_DEBUG_FSC, "inode %p put cookie %p\n",
inode, v9inode->fscache);
fscache_relinquish_cookie(v9inode->fscache, 0);
v9inode->fscache = NULL;
}
void v9fs_cache_inode_flush_cookie(struct inode *inode)
{
struct v9fs_inode *v9inode = V9FS_I(inode);
if (!v9inode->fscache)
return;
p9_debug(P9_DEBUG_FSC, "inode %p flush cookie %p\n",
inode, v9inode->fscache);
fscache_relinquish_cookie(v9inode->fscache, 1);
v9inode->fscache = NULL;
}
void v9fs_cache_inode_set_cookie(struct inode *inode, struct file *filp)
{
struct v9fs_inode *v9inode = V9FS_I(inode);
if (!v9inode->fscache)
return;
mutex_lock(&v9inode->fscache_lock);
if ((filp->f_flags & O_ACCMODE) != O_RDONLY)
v9fs_cache_inode_flush_cookie(inode);
else
v9fs_cache_inode_get_cookie(inode);
mutex_unlock(&v9inode->fscache_lock);
}
void v9fs_cache_inode_reset_cookie(struct inode *inode)
{
struct v9fs_inode *v9inode = V9FS_I(inode);
struct v9fs_session_info *v9ses;
struct fscache_cookie *old;
if (!v9inode->fscache)
return;
old = v9inode->fscache;
mutex_lock(&v9inode->fscache_lock);
fscache_relinquish_cookie(v9inode->fscache, 1);
v9ses = v9fs_inode2v9ses(inode);
v9inode->fscache = fscache_acquire_cookie(v9ses->fscache,
&v9fs_cache_inode_index_def,
v9inode, true);
p9_debug(P9_DEBUG_FSC, "inode %p revalidating cookie old %p new %p\n",
inode, old, v9inode->fscache);
mutex_unlock(&v9inode->fscache_lock);
}
int __v9fs_fscache_release_page(struct page *page, gfp_t gfp)
{
struct inode *inode = page->mapping->host;
struct v9fs_inode *v9inode = V9FS_I(inode);
BUG_ON(!v9inode->fscache);
return fscache_maybe_release_page(v9inode->fscache, page, gfp);
}
void __v9fs_fscache_invalidate_page(struct page *page)
{
struct inode *inode = page->mapping->host;
struct v9fs_inode *v9inode = V9FS_I(inode);
BUG_ON(!v9inode->fscache);
if (PageFsCache(page)) {
fscache_wait_on_page_write(v9inode->fscache, page);
BUG_ON(!PageLocked(page));
fscache_uncache_page(v9inode->fscache, page);
}
}
static void v9fs_vfs_readpage_complete(struct page *page, void *data,
int error)
{
if (!error)
SetPageUptodate(page);
unlock_page(page);
}
/**
* __v9fs_readpage_from_fscache - read a page from cache
*
* Returns 0 if the pages are in cache and a BIO is submitted,
* 1 if the pages are not in cache and -error otherwise.
*/
int __v9fs_readpage_from_fscache(struct inode *inode, struct page *page)
{
int ret;
const struct v9fs_inode *v9inode = V9FS_I(inode);
p9_debug(P9_DEBUG_FSC, "inode %p page %p\n", inode, page);
if (!v9inode->fscache)
return -ENOBUFS;
ret = fscache_read_or_alloc_page(v9inode->fscache,
page,
v9fs_vfs_readpage_complete,
NULL,
GFP_KERNEL);
switch (ret) {
case -ENOBUFS:
case -ENODATA:
p9_debug(P9_DEBUG_FSC, "page/inode not in cache %d\n", ret);
return 1;
case 0:
p9_debug(P9_DEBUG_FSC, "BIO submitted\n");
return ret;
default:
p9_debug(P9_DEBUG_FSC, "ret %d\n", ret);
return ret;
}
}
/**
* __v9fs_readpages_from_fscache - read multiple pages from cache
*
* Returns 0 if the pages are in cache and a BIO is submitted,
* 1 if the pages are not in cache and -error otherwise.
*/
int __v9fs_readpages_from_fscache(struct inode *inode,
struct address_space *mapping,
struct list_head *pages,
unsigned *nr_pages)
{
int ret;
const struct v9fs_inode *v9inode = V9FS_I(inode);
p9_debug(P9_DEBUG_FSC, "inode %p pages %u\n", inode, *nr_pages);
if (!v9inode->fscache)
return -ENOBUFS;
ret = fscache_read_or_alloc_pages(v9inode->fscache,
mapping, pages, nr_pages,
v9fs_vfs_readpage_complete,
NULL,
mapping_gfp_mask(mapping));
switch (ret) {
case -ENOBUFS:
case -ENODATA:
p9_debug(P9_DEBUG_FSC, "pages/inodes not in cache %d\n", ret);
return 1;
case 0:
BUG_ON(!list_empty(pages));
BUG_ON(*nr_pages != 0);
p9_debug(P9_DEBUG_FSC, "BIO submitted\n");
return ret;
default:
p9_debug(P9_DEBUG_FSC, "ret %d\n", ret);
return ret;
}
}
/**
* __v9fs_readpage_to_fscache - write a page to the cache
*
*/
void __v9fs_readpage_to_fscache(struct inode *inode, struct page *page)
{
int ret;
const struct v9fs_inode *v9inode = V9FS_I(inode);
p9_debug(P9_DEBUG_FSC, "inode %p page %p\n", inode, page);
ret = fscache_write_page(v9inode->fscache, page, GFP_KERNEL);
p9_debug(P9_DEBUG_FSC, "ret = %d\n", ret);
if (ret != 0)
v9fs_uncache_page(inode, page);
}
/*
* wait for a page to complete writing to the cache
*/
void __v9fs_fscache_wait_on_page_write(struct inode *inode, struct page *page)
{
const struct v9fs_inode *v9inode = V9FS_I(inode);
p9_debug(P9_DEBUG_FSC, "inode %p page %p\n", inode, page);
if (PageFsCache(page))
fscache_wait_on_page_write(v9inode->fscache, page);
}

View File

@@ -0,0 +1,152 @@
/*
* V9FS cache definitions.
*
* Copyright (C) 2009 by Abhishek Kulkarni <adkulkar@umail.iu.edu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#ifndef _9P_CACHE_H
#define _9P_CACHE_H
#ifdef CONFIG_9P_FSCACHE
#include <linux/fscache.h>
#include <linux/spinlock.h>
extern struct fscache_netfs v9fs_cache_netfs;
extern const struct fscache_cookie_def v9fs_cache_session_index_def;
extern const struct fscache_cookie_def v9fs_cache_inode_index_def;
extern void v9fs_cache_session_get_cookie(struct v9fs_session_info *v9ses);
extern void v9fs_cache_session_put_cookie(struct v9fs_session_info *v9ses);
extern void v9fs_cache_inode_get_cookie(struct inode *inode);
extern void v9fs_cache_inode_put_cookie(struct inode *inode);
extern void v9fs_cache_inode_flush_cookie(struct inode *inode);
extern void v9fs_cache_inode_set_cookie(struct inode *inode, struct file *filp);
extern void v9fs_cache_inode_reset_cookie(struct inode *inode);
extern int __v9fs_cache_register(void);
extern void __v9fs_cache_unregister(void);
extern int __v9fs_fscache_release_page(struct page *page, gfp_t gfp);
extern void __v9fs_fscache_invalidate_page(struct page *page);
extern int __v9fs_readpage_from_fscache(struct inode *inode,
struct page *page);
extern int __v9fs_readpages_from_fscache(struct inode *inode,
struct address_space *mapping,
struct list_head *pages,
unsigned *nr_pages);
extern void __v9fs_readpage_to_fscache(struct inode *inode, struct page *page);
extern void __v9fs_fscache_wait_on_page_write(struct inode *inode,
struct page *page);
static inline int v9fs_fscache_release_page(struct page *page,
gfp_t gfp)
{
return __v9fs_fscache_release_page(page, gfp);
}
static inline void v9fs_fscache_invalidate_page(struct page *page)
{
__v9fs_fscache_invalidate_page(page);
}
static inline int v9fs_readpage_from_fscache(struct inode *inode,
struct page *page)
{
return __v9fs_readpage_from_fscache(inode, page);
}
static inline int v9fs_readpages_from_fscache(struct inode *inode,
struct address_space *mapping,
struct list_head *pages,
unsigned *nr_pages)
{
return __v9fs_readpages_from_fscache(inode, mapping, pages,
nr_pages);
}
static inline void v9fs_readpage_to_fscache(struct inode *inode,
struct page *page)
{
if (PageFsCache(page))
__v9fs_readpage_to_fscache(inode, page);
}
static inline void v9fs_uncache_page(struct inode *inode, struct page *page)
{
struct v9fs_inode *v9inode = V9FS_I(inode);
fscache_uncache_page(v9inode->fscache, page);
BUG_ON(PageFsCache(page));
}
static inline void v9fs_fscache_wait_on_page_write(struct inode *inode,
struct page *page)
{
return __v9fs_fscache_wait_on_page_write(inode, page);
}
#else /* CONFIG_9P_FSCACHE */
static inline void v9fs_cache_inode_get_cookie(struct inode *inode)
{
}
static inline void v9fs_cache_inode_put_cookie(struct inode *inode)
{
}
static inline void v9fs_cache_inode_set_cookie(struct inode *inode, struct file *file)
{
}
static inline int v9fs_fscache_release_page(struct page *page,
gfp_t gfp) {
return 1;
}
static inline void v9fs_fscache_invalidate_page(struct page *page) {}
static inline int v9fs_readpage_from_fscache(struct inode *inode,
struct page *page)
{
return -ENOBUFS;
}
static inline int v9fs_readpages_from_fscache(struct inode *inode,
struct address_space *mapping,
struct list_head *pages,
unsigned *nr_pages)
{
return -ENOBUFS;
}
static inline void v9fs_readpage_to_fscache(struct inode *inode,
struct page *page)
{}
static inline void v9fs_uncache_page(struct inode *inode, struct page *page)
{}
static inline void v9fs_fscache_wait_on_page_write(struct inode *inode,
struct page *page)
{
return;
}
#endif /* CONFIG_9P_FSCACHE */
#endif /* _9P_CACHE_H */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,247 @@
/*
* linux/fs/9p/error.c
*
* Error string handling
*
* Plan 9 uses error strings, Unix uses error numbers. These functions
* try to help manage that and provide for dynamically adding error
* mappings.
*
* Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
* Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/list.h>
#include <linux/jhash.h>
#include <linux/errno.h>
#include <net/9p/9p.h>
/**
* struct errormap - map string errors from Plan 9 to Linux numeric ids
* @name: string sent over 9P
* @val: numeric id most closely representing @name
* @namelen: length of string
* @list: hash-table list for string lookup
*/
struct errormap {
char *name;
int val;
int namelen;
struct hlist_node list;
};
#define ERRHASHSZ 32
static struct hlist_head hash_errmap[ERRHASHSZ];
/* FixMe - reduce to a reasonable size */
static struct errormap errmap[] = {
{"Operation not permitted", EPERM},
{"wstat prohibited", EPERM},
{"No such file or directory", ENOENT},
{"directory entry not found", ENOENT},
{"file not found", ENOENT},
{"Interrupted system call", EINTR},
{"Input/output error", EIO},
{"No such device or address", ENXIO},
{"Argument list too long", E2BIG},
{"Bad file descriptor", EBADF},
{"Resource temporarily unavailable", EAGAIN},
{"Cannot allocate memory", ENOMEM},
{"Permission denied", EACCES},
{"Bad address", EFAULT},
{"Block device required", ENOTBLK},
{"Device or resource busy", EBUSY},
{"File exists", EEXIST},
{"Invalid cross-device link", EXDEV},
{"No such device", ENODEV},
{"Not a directory", ENOTDIR},
{"Is a directory", EISDIR},
{"Invalid argument", EINVAL},
{"Too many open files in system", ENFILE},
{"Too many open files", EMFILE},
{"Text file busy", ETXTBSY},
{"File too large", EFBIG},
{"No space left on device", ENOSPC},
{"Illegal seek", ESPIPE},
{"Read-only file system", EROFS},
{"Too many links", EMLINK},
{"Broken pipe", EPIPE},
{"Numerical argument out of domain", EDOM},
{"Numerical result out of range", ERANGE},
{"Resource deadlock avoided", EDEADLK},
{"File name too long", ENAMETOOLONG},
{"No locks available", ENOLCK},
{"Function not implemented", ENOSYS},
{"Directory not empty", ENOTEMPTY},
{"Too many levels of symbolic links", ELOOP},
{"No message of desired type", ENOMSG},
{"Identifier removed", EIDRM},
{"No data available", ENODATA},
{"Machine is not on the network", ENONET},
{"Package not installed", ENOPKG},
{"Object is remote", EREMOTE},
{"Link has been severed", ENOLINK},
{"Communication error on send", ECOMM},
{"Protocol error", EPROTO},
{"Bad message", EBADMSG},
{"File descriptor in bad state", EBADFD},
{"Streams pipe error", ESTRPIPE},
{"Too many users", EUSERS},
{"Socket operation on non-socket", ENOTSOCK},
{"Message too long", EMSGSIZE},
{"Protocol not available", ENOPROTOOPT},
{"Protocol not supported", EPROTONOSUPPORT},
{"Socket type not supported", ESOCKTNOSUPPORT},
{"Operation not supported", EOPNOTSUPP},
{"Protocol family not supported", EPFNOSUPPORT},
{"Network is down", ENETDOWN},
{"Network is unreachable", ENETUNREACH},
{"Network dropped connection on reset", ENETRESET},
{"Software caused connection abort", ECONNABORTED},
{"Connection reset by peer", ECONNRESET},
{"No buffer space available", ENOBUFS},
{"Transport endpoint is already connected", EISCONN},
{"Transport endpoint is not connected", ENOTCONN},
{"Cannot send after transport endpoint shutdown", ESHUTDOWN},
{"Connection timed out", ETIMEDOUT},
{"Connection refused", ECONNREFUSED},
{"Host is down", EHOSTDOWN},
{"No route to host", EHOSTUNREACH},
{"Operation already in progress", EALREADY},
{"Operation now in progress", EINPROGRESS},
{"Is a named type file", EISNAM},
{"Remote I/O error", EREMOTEIO},
{"Disk quota exceeded", EDQUOT},
/* errors from fossil, vacfs, and u9fs */
{"fid unknown or out of range", EBADF},
{"permission denied", EACCES},
{"file does not exist", ENOENT},
{"authentication failed", ECONNREFUSED},
{"bad offset in directory read", ESPIPE},
{"bad use of fid", EBADF},
{"wstat can't convert between files and directories", EPERM},
{"directory is not empty", ENOTEMPTY},
{"file exists", EEXIST},
{"file already exists", EEXIST},
{"file or directory already exists", EEXIST},
{"fid already in use", EBADF},
{"file in use", ETXTBSY},
{"i/o error", EIO},
{"file already open for I/O", ETXTBSY},
{"illegal mode", EINVAL},
{"illegal name", ENAMETOOLONG},
{"not a directory", ENOTDIR},
{"not a member of proposed group", EPERM},
{"not owner", EACCES},
{"only owner can change group in wstat", EACCES},
{"read only file system", EROFS},
{"no access to special file", EPERM},
{"i/o count too large", EIO},
{"unknown group", EINVAL},
{"unknown user", EINVAL},
{"bogus wstat buffer", EPROTO},
{"exclusive use file already open", EAGAIN},
{"corrupted directory entry", EIO},
{"corrupted file entry", EIO},
{"corrupted block label", EIO},
{"corrupted meta data", EIO},
{"illegal offset", EINVAL},
{"illegal path element", ENOENT},
{"root of file system is corrupted", EIO},
{"corrupted super block", EIO},
{"protocol botch", EPROTO},
{"file system is full", ENOSPC},
{"file is in use", EAGAIN},
{"directory entry is not allocated", ENOENT},
{"file is read only", EROFS},
{"file has been removed", EIDRM},
{"only support truncation to zero length", EPERM},
{"cannot remove root", EPERM},
{"file too big", EFBIG},
{"venti i/o error", EIO},
/* these are not errors */
{"u9fs rhostsauth: no authentication required", 0},
{"u9fs authnone: no authentication required", 0},
{NULL, -1}
};
/**
* p9_error_init - preload mappings into hash list
*
*/
int p9_error_init(void)
{
struct errormap *c;
int bucket;
/* initialize hash table */
for (bucket = 0; bucket < ERRHASHSZ; bucket++)
INIT_HLIST_HEAD(&hash_errmap[bucket]);
/* load initial error map into hash table */
for (c = errmap; c->name != NULL; c++) {
c->namelen = strlen(c->name);
bucket = jhash(c->name, c->namelen, 0) % ERRHASHSZ;
INIT_HLIST_NODE(&c->list);
hlist_add_head(&c->list, &hash_errmap[bucket]);
}
return 1;
}
EXPORT_SYMBOL(p9_error_init);
/**
* errstr2errno - convert error string to error number
* @errstr: error string
* @len: length of error string
*
*/
int p9_errstr2errno(char *errstr, int len)
{
int errno;
struct errormap *c;
int bucket;
errno = 0;
c = NULL;
bucket = jhash(errstr, len, 0) % ERRHASHSZ;
hlist_for_each_entry(c, &hash_errmap[bucket], list) {
if (c->namelen == len && !memcmp(c->name, errstr, len)) {
errno = c->val;
break;
}
}
if (errno == 0) {
/* TODO: if error isn't found, add it dynamically */
errstr[len] = 0;
pr_err("%s: server reported unknown error %s\n",
__func__, errstr);
errno = ESERVERFAULT;
}
return -errno;
}
EXPORT_SYMBOL(p9_errstr2errno);

306
addons/9p/src/4.4.180/fid.c Normal file
View File

@@ -0,0 +1,306 @@
/*
* V9FS FID Management
*
* Copyright (C) 2007 by Latchesar Ionkov <lucho@ionkov.net>
* Copyright (C) 2005, 2006 by Eric Van Hensbergen <ericvh@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/idr.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
#include "v9fs.h"
#include "v9fs_vfs.h"
#include "fid.h"
/**
* v9fs_fid_add - add a fid to a dentry
* @dentry: dentry that the fid is being added to
* @fid: fid to add
*
*/
static inline void __add_fid(struct dentry *dentry, struct p9_fid *fid)
{
hlist_add_head(&fid->dlist, (struct hlist_head *)&dentry->d_fsdata);
}
void v9fs_fid_add(struct dentry *dentry, struct p9_fid *fid)
{
spin_lock(&dentry->d_lock);
__add_fid(dentry, fid);
spin_unlock(&dentry->d_lock);
}
/**
* v9fs_fid_find - retrieve a fid that belongs to the specified uid
* @dentry: dentry to look for fid in
* @uid: return fid that belongs to the specified user
* @any: if non-zero, return any fid associated with the dentry
*
*/
static struct p9_fid *v9fs_fid_find(struct dentry *dentry, kuid_t uid, int any)
{
struct p9_fid *fid, *ret;
p9_debug(P9_DEBUG_VFS, " dentry: %pd (%p) uid %d any %d\n",
dentry, dentry, from_kuid(&init_user_ns, uid),
any);
ret = NULL;
/* we'll recheck under lock if there's anything to look in */
if (dentry->d_fsdata) {
struct hlist_head *h = (struct hlist_head *)&dentry->d_fsdata;
spin_lock(&dentry->d_lock);
hlist_for_each_entry(fid, h, dlist) {
if (any || uid_eq(fid->uid, uid)) {
ret = fid;
break;
}
}
spin_unlock(&dentry->d_lock);
}
return ret;
}
/*
* We need to hold v9ses->rename_sem as long as we hold references
* to returned path array. Array element contain pointers to
* dentry names.
*/
static int build_path_from_dentry(struct v9fs_session_info *v9ses,
struct dentry *dentry, char ***names)
{
int n = 0, i;
char **wnames;
struct dentry *ds;
for (ds = dentry; !IS_ROOT(ds); ds = ds->d_parent)
n++;
wnames = kmalloc(sizeof(char *) * n, GFP_KERNEL);
if (!wnames)
goto err_out;
for (ds = dentry, i = (n-1); i >= 0; i--, ds = ds->d_parent)
wnames[i] = (char *)ds->d_name.name;
*names = wnames;
return n;
err_out:
return -ENOMEM;
}
static struct p9_fid *v9fs_fid_lookup_with_uid(struct dentry *dentry,
kuid_t uid, int any)
{
struct dentry *ds;
char **wnames, *uname;
int i, n, l, clone, access;
struct v9fs_session_info *v9ses;
struct p9_fid *fid, *old_fid = NULL;
v9ses = v9fs_dentry2v9ses(dentry);
access = v9ses->flags & V9FS_ACCESS_MASK;
fid = v9fs_fid_find(dentry, uid, any);
if (fid)
return fid;
/*
* we don't have a matching fid. To do a TWALK we need
* parent fid. We need to prevent rename when we want to
* look at the parent.
*/
down_read(&v9ses->rename_sem);
ds = dentry->d_parent;
fid = v9fs_fid_find(ds, uid, any);
if (fid) {
/* Found the parent fid do a lookup with that */
fid = p9_client_walk(fid, 1, (char **)&dentry->d_name.name, 1);
goto fid_out;
}
up_read(&v9ses->rename_sem);
/* start from the root and try to do a lookup */
fid = v9fs_fid_find(dentry->d_sb->s_root, uid, any);
if (!fid) {
/* the user is not attached to the fs yet */
if (access == V9FS_ACCESS_SINGLE)
return ERR_PTR(-EPERM);
if (v9fs_proto_dotu(v9ses) || v9fs_proto_dotl(v9ses))
uname = NULL;
else
uname = v9ses->uname;
fid = p9_client_attach(v9ses->clnt, NULL, uname, uid,
v9ses->aname);
if (IS_ERR(fid))
return fid;
v9fs_fid_add(dentry->d_sb->s_root, fid);
}
/* If we are root ourself just return that */
if (dentry->d_sb->s_root == dentry)
return fid;
/*
* Do a multipath walk with attached root.
* When walking parent we need to make sure we
* don't have a parallel rename happening
*/
down_read(&v9ses->rename_sem);
n = build_path_from_dentry(v9ses, dentry, &wnames);
if (n < 0) {
fid = ERR_PTR(n);
goto err_out;
}
clone = 1;
i = 0;
while (i < n) {
l = min(n - i, P9_MAXWELEM);
/*
* We need to hold rename lock when doing a multipath
* walk to ensure none of the patch component change
*/
fid = p9_client_walk(fid, l, &wnames[i], clone);
if (IS_ERR(fid)) {
if (old_fid) {
/*
* If we fail, clunk fid which are mapping
* to path component and not the last component
* of the path.
*/
p9_client_clunk(old_fid);
}
kfree(wnames);
goto err_out;
}
old_fid = fid;
i += l;
clone = 0;
}
kfree(wnames);
fid_out:
if (!IS_ERR(fid)) {
spin_lock(&dentry->d_lock);
if (d_unhashed(dentry)) {
spin_unlock(&dentry->d_lock);
p9_client_clunk(fid);
fid = ERR_PTR(-ENOENT);
} else {
__add_fid(dentry, fid);
spin_unlock(&dentry->d_lock);
}
}
err_out:
up_read(&v9ses->rename_sem);
return fid;
}
/**
* v9fs_fid_lookup - lookup for a fid, try to walk if not found
* @dentry: dentry to look for fid in
*
* Look for a fid in the specified dentry for the current user.
* If no fid is found, try to create one walking from a fid from the parent
* dentry (if it has one), or the root dentry. If the user haven't accessed
* the fs yet, attach now and walk from the root.
*/
struct p9_fid *v9fs_fid_lookup(struct dentry *dentry)
{
kuid_t uid;
int any, access;
struct v9fs_session_info *v9ses;
v9ses = v9fs_dentry2v9ses(dentry);
access = v9ses->flags & V9FS_ACCESS_MASK;
switch (access) {
case V9FS_ACCESS_SINGLE:
case V9FS_ACCESS_USER:
case V9FS_ACCESS_CLIENT:
uid = current_fsuid();
any = 0;
break;
case V9FS_ACCESS_ANY:
uid = v9ses->uid;
any = 1;
break;
default:
uid = INVALID_UID;
any = 0;
break;
}
return v9fs_fid_lookup_with_uid(dentry, uid, any);
}
struct p9_fid *v9fs_fid_clone(struct dentry *dentry)
{
struct p9_fid *fid, *ret;
fid = v9fs_fid_lookup(dentry);
if (IS_ERR(fid))
return fid;
ret = p9_client_walk(fid, 0, NULL, 1);
return ret;
}
static struct p9_fid *v9fs_fid_clone_with_uid(struct dentry *dentry, kuid_t uid)
{
struct p9_fid *fid, *ret;
fid = v9fs_fid_lookup_with_uid(dentry, uid, 0);
if (IS_ERR(fid))
return fid;
ret = p9_client_walk(fid, 0, NULL, 1);
return ret;
}
struct p9_fid *v9fs_writeback_fid(struct dentry *dentry)
{
int err;
struct p9_fid *fid;
fid = v9fs_fid_clone_with_uid(dentry, GLOBAL_ROOT_UID);
if (IS_ERR(fid))
goto error_out;
/*
* writeback fid will only be used to write back the
* dirty pages. We always request for the open fid in read-write
* mode so that a partial page write which result in page
* read can work.
*/
err = p9_client_open(fid, O_RDWR);
if (err < 0) {
p9_client_clunk(fid);
fid = ERR_PTR(err);
goto error_out;
}
error_out:
return fid;
}

View File

@@ -0,0 +1,30 @@
/*
* V9FS FID Management
*
* Copyright (C) 2005 by Eric Van Hensbergen <ericvh@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#ifndef FS_9P_FID_H
#define FS_9P_FID_H
#include <linux/list.h>
struct p9_fid *v9fs_fid_lookup(struct dentry *dentry);
struct p9_fid *v9fs_fid_clone(struct dentry *dentry);
void v9fs_fid_add(struct dentry *dentry, struct p9_fid *fid);
struct p9_fid *v9fs_writeback_fid(struct dentry *dentry);
#endif

201
addons/9p/src/4.4.180/mod.c Normal file
View File

@@ -0,0 +1,201 @@
/*
* net/9p/9p.c
*
* 9P entry point
*
* Copyright (C) 2007 by Latchesar Ionkov <lucho@ionkov.net>
* Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
* Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/moduleparam.h>
#include <net/9p/9p.h>
#include <linux/fs.h>
#include <linux/parser.h>
#include <net/9p/client.h>
#include <net/9p/transport.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#ifdef CONFIG_NET_9P_DEBUG
unsigned int p9_debug_level = 0; /* feature-rific global debug level */
EXPORT_SYMBOL(p9_debug_level);
module_param_named(debug, p9_debug_level, uint, 0);
MODULE_PARM_DESC(debug, "9P debugging level");
void _p9_debug(enum p9_debug_flags level, const char *func,
const char *fmt, ...)
{
struct va_format vaf;
va_list args;
if ((p9_debug_level & level) != level)
return;
va_start(args, fmt);
vaf.fmt = fmt;
vaf.va = &args;
if (level == P9_DEBUG_9P)
pr_notice("(%8.8d) %pV", task_pid_nr(current), &vaf);
else
pr_notice("-- %s (%d): %pV", func, task_pid_nr(current), &vaf);
va_end(args);
}
EXPORT_SYMBOL(_p9_debug);
#endif
/*
* Dynamic Transport Registration Routines
*
*/
static DEFINE_SPINLOCK(v9fs_trans_lock);
static LIST_HEAD(v9fs_trans_list);
/**
* v9fs_register_trans - register a new transport with 9p
* @m: structure describing the transport module and entry points
*
*/
void v9fs_register_trans(struct p9_trans_module *m)
{
spin_lock(&v9fs_trans_lock);
list_add_tail(&m->list, &v9fs_trans_list);
spin_unlock(&v9fs_trans_lock);
}
EXPORT_SYMBOL(v9fs_register_trans);
/**
* v9fs_unregister_trans - unregister a 9p transport
* @m: the transport to remove
*
*/
void v9fs_unregister_trans(struct p9_trans_module *m)
{
spin_lock(&v9fs_trans_lock);
list_del_init(&m->list);
spin_unlock(&v9fs_trans_lock);
}
EXPORT_SYMBOL(v9fs_unregister_trans);
/**
* v9fs_get_trans_by_name - get transport with the matching name
* @name: string identifying transport
*
*/
struct p9_trans_module *v9fs_get_trans_by_name(char *s)
{
struct p9_trans_module *t, *found = NULL;
spin_lock(&v9fs_trans_lock);
list_for_each_entry(t, &v9fs_trans_list, list)
if (strcmp(t->name, s) == 0 &&
try_module_get(t->owner)) {
found = t;
break;
}
spin_unlock(&v9fs_trans_lock);
return found;
}
EXPORT_SYMBOL(v9fs_get_trans_by_name);
/**
* v9fs_get_default_trans - get the default transport
*
*/
struct p9_trans_module *v9fs_get_default_trans(void)
{
struct p9_trans_module *t, *found = NULL;
spin_lock(&v9fs_trans_lock);
list_for_each_entry(t, &v9fs_trans_list, list)
if (t->def && try_module_get(t->owner)) {
found = t;
break;
}
if (!found)
list_for_each_entry(t, &v9fs_trans_list, list)
if (try_module_get(t->owner)) {
found = t;
break;
}
spin_unlock(&v9fs_trans_lock);
return found;
}
EXPORT_SYMBOL(v9fs_get_default_trans);
/**
* v9fs_put_trans - put trans
* @m: transport to put
*
*/
void v9fs_put_trans(struct p9_trans_module *m)
{
if (m)
module_put(m->owner);
}
/**
* init_p9 - Initialize module
*
*/
static int __init init_p9(void)
{
int ret = 0;
p9_error_init();
pr_info("Installing 9P2000 support\n");
p9_trans_fd_init();
return ret;
}
/**
* exit_p9 - shutdown module
*
*/
static void __exit exit_p9(void)
{
pr_info("Unloading 9P2000 support\n");
p9_trans_fd_exit();
}
module_init(init_p9)
module_exit(exit_p9)
MODULE_AUTHOR("Latchesar Ionkov <lucho@ionkov.net>");
MODULE_AUTHOR("Eric Van Hensbergen <ericvh@gmail.com>");
MODULE_AUTHOR("Ron Minnich <rminnich@lanl.gov>");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,634 @@
/*
* net/9p/protocol.c
*
* 9P Protocol Support Code
*
* Copyright (C) 2008 by Eric Van Hensbergen <ericvh@gmail.com>
*
* Base on code from Anthony Liguori <aliguori@us.ibm.com>
* Copyright (C) 2008 by IBM, Corp.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/stddef.h>
#include <linux/types.h>
#include <linux/uio.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
#include "protocol.h"
#include <trace/events/9p.h>
static int
p9pdu_writef(struct p9_fcall *pdu, int proto_version, const char *fmt, ...);
void p9stat_free(struct p9_wstat *stbuf)
{
kfree(stbuf->name);
stbuf->name = NULL;
kfree(stbuf->uid);
stbuf->uid = NULL;
kfree(stbuf->gid);
stbuf->gid = NULL;
kfree(stbuf->muid);
stbuf->muid = NULL;
kfree(stbuf->extension);
stbuf->extension = NULL;
}
EXPORT_SYMBOL(p9stat_free);
size_t pdu_read(struct p9_fcall *pdu, void *data, size_t size)
{
size_t len = min(pdu->size - pdu->offset, size);
memcpy(data, &pdu->sdata[pdu->offset], len);
pdu->offset += len;
return size - len;
}
static size_t pdu_write(struct p9_fcall *pdu, const void *data, size_t size)
{
size_t len = min(pdu->capacity - pdu->size, size);
memcpy(&pdu->sdata[pdu->size], data, len);
pdu->size += len;
return size - len;
}
static size_t
pdu_write_u(struct p9_fcall *pdu, struct iov_iter *from, size_t size)
{
size_t len = min(pdu->capacity - pdu->size, size);
struct iov_iter i = *from;
if (copy_from_iter(&pdu->sdata[pdu->size], len, &i) != len)
len = 0;
pdu->size += len;
return size - len;
}
/*
b - int8_t
w - int16_t
d - int32_t
q - int64_t
s - string
u - numeric uid
g - numeric gid
S - stat
Q - qid
D - data blob (int32_t size followed by void *, results are not freed)
T - array of strings (int16_t count, followed by strings)
R - array of qids (int16_t count, followed by qids)
A - stat for 9p2000.L (p9_stat_dotl)
? - if optional = 1, continue parsing
*/
static int
p9pdu_vreadf(struct p9_fcall *pdu, int proto_version, const char *fmt,
va_list ap)
{
const char *ptr;
int errcode = 0;
for (ptr = fmt; *ptr; ptr++) {
switch (*ptr) {
case 'b':{
int8_t *val = va_arg(ap, int8_t *);
if (pdu_read(pdu, val, sizeof(*val))) {
errcode = -EFAULT;
break;
}
}
break;
case 'w':{
int16_t *val = va_arg(ap, int16_t *);
__le16 le_val;
if (pdu_read(pdu, &le_val, sizeof(le_val))) {
errcode = -EFAULT;
break;
}
*val = le16_to_cpu(le_val);
}
break;
case 'd':{
int32_t *val = va_arg(ap, int32_t *);
__le32 le_val;
if (pdu_read(pdu, &le_val, sizeof(le_val))) {
errcode = -EFAULT;
break;
}
*val = le32_to_cpu(le_val);
}
break;
case 'q':{
int64_t *val = va_arg(ap, int64_t *);
__le64 le_val;
if (pdu_read(pdu, &le_val, sizeof(le_val))) {
errcode = -EFAULT;
break;
}
*val = le64_to_cpu(le_val);
}
break;
case 's':{
char **sptr = va_arg(ap, char **);
uint16_t len;
errcode = p9pdu_readf(pdu, proto_version,
"w", &len);
if (errcode)
break;
*sptr = kmalloc(len + 1, GFP_NOFS);
if (*sptr == NULL) {
errcode = -EFAULT;
break;
}
if (pdu_read(pdu, *sptr, len)) {
errcode = -EFAULT;
kfree(*sptr);
*sptr = NULL;
} else
(*sptr)[len] = 0;
}
break;
case 'u': {
kuid_t *uid = va_arg(ap, kuid_t *);
__le32 le_val;
if (pdu_read(pdu, &le_val, sizeof(le_val))) {
errcode = -EFAULT;
break;
}
*uid = make_kuid(&init_user_ns,
le32_to_cpu(le_val));
} break;
case 'g': {
kgid_t *gid = va_arg(ap, kgid_t *);
__le32 le_val;
if (pdu_read(pdu, &le_val, sizeof(le_val))) {
errcode = -EFAULT;
break;
}
*gid = make_kgid(&init_user_ns,
le32_to_cpu(le_val));
} break;
case 'Q':{
struct p9_qid *qid =
va_arg(ap, struct p9_qid *);
errcode = p9pdu_readf(pdu, proto_version, "bdq",
&qid->type, &qid->version,
&qid->path);
}
break;
case 'S':{
struct p9_wstat *stbuf =
va_arg(ap, struct p9_wstat *);
memset(stbuf, 0, sizeof(struct p9_wstat));
stbuf->n_uid = stbuf->n_muid = INVALID_UID;
stbuf->n_gid = INVALID_GID;
errcode =
p9pdu_readf(pdu, proto_version,
"wwdQdddqssss?sugu",
&stbuf->size, &stbuf->type,
&stbuf->dev, &stbuf->qid,
&stbuf->mode, &stbuf->atime,
&stbuf->mtime, &stbuf->length,
&stbuf->name, &stbuf->uid,
&stbuf->gid, &stbuf->muid,
&stbuf->extension,
&stbuf->n_uid, &stbuf->n_gid,
&stbuf->n_muid);
if (errcode)
p9stat_free(stbuf);
}
break;
case 'D':{
uint32_t *count = va_arg(ap, uint32_t *);
void **data = va_arg(ap, void **);
errcode =
p9pdu_readf(pdu, proto_version, "d", count);
if (!errcode) {
*count =
min_t(uint32_t, *count,
pdu->size - pdu->offset);
*data = &pdu->sdata[pdu->offset];
}
}
break;
case 'T':{
uint16_t *nwname = va_arg(ap, uint16_t *);
char ***wnames = va_arg(ap, char ***);
errcode = p9pdu_readf(pdu, proto_version,
"w", nwname);
if (!errcode) {
*wnames =
kmalloc(sizeof(char *) * *nwname,
GFP_NOFS);
if (!*wnames)
errcode = -ENOMEM;
}
if (!errcode) {
int i;
for (i = 0; i < *nwname; i++) {
errcode =
p9pdu_readf(pdu,
proto_version,
"s",
&(*wnames)[i]);
if (errcode)
break;
}
}
if (errcode) {
if (*wnames) {
int i;
for (i = 0; i < *nwname; i++)
kfree((*wnames)[i]);
}
kfree(*wnames);
*wnames = NULL;
}
}
break;
case 'R':{
uint16_t *nwqid = va_arg(ap, uint16_t *);
struct p9_qid **wqids =
va_arg(ap, struct p9_qid **);
*wqids = NULL;
errcode =
p9pdu_readf(pdu, proto_version, "w", nwqid);
if (!errcode) {
*wqids =
kmalloc(*nwqid *
sizeof(struct p9_qid),
GFP_NOFS);
if (*wqids == NULL)
errcode = -ENOMEM;
}
if (!errcode) {
int i;
for (i = 0; i < *nwqid; i++) {
errcode =
p9pdu_readf(pdu,
proto_version,
"Q",
&(*wqids)[i]);
if (errcode)
break;
}
}
if (errcode) {
kfree(*wqids);
*wqids = NULL;
}
}
break;
case 'A': {
struct p9_stat_dotl *stbuf =
va_arg(ap, struct p9_stat_dotl *);
memset(stbuf, 0, sizeof(struct p9_stat_dotl));
errcode =
p9pdu_readf(pdu, proto_version,
"qQdugqqqqqqqqqqqqqqq",
&stbuf->st_result_mask,
&stbuf->qid,
&stbuf->st_mode,
&stbuf->st_uid, &stbuf->st_gid,
&stbuf->st_nlink,
&stbuf->st_rdev, &stbuf->st_size,
&stbuf->st_blksize, &stbuf->st_blocks,
&stbuf->st_atime_sec,
&stbuf->st_atime_nsec,
&stbuf->st_mtime_sec,
&stbuf->st_mtime_nsec,
&stbuf->st_ctime_sec,
&stbuf->st_ctime_nsec,
&stbuf->st_btime_sec,
&stbuf->st_btime_nsec,
&stbuf->st_gen,
&stbuf->st_data_version);
}
break;
case '?':
if ((proto_version != p9_proto_2000u) &&
(proto_version != p9_proto_2000L))
return 0;
break;
default:
BUG();
break;
}
if (errcode)
break;
}
return errcode;
}
int
p9pdu_vwritef(struct p9_fcall *pdu, int proto_version, const char *fmt,
va_list ap)
{
const char *ptr;
int errcode = 0;
for (ptr = fmt; *ptr; ptr++) {
switch (*ptr) {
case 'b':{
int8_t val = va_arg(ap, int);
if (pdu_write(pdu, &val, sizeof(val)))
errcode = -EFAULT;
}
break;
case 'w':{
__le16 val = cpu_to_le16(va_arg(ap, int));
if (pdu_write(pdu, &val, sizeof(val)))
errcode = -EFAULT;
}
break;
case 'd':{
__le32 val = cpu_to_le32(va_arg(ap, int32_t));
if (pdu_write(pdu, &val, sizeof(val)))
errcode = -EFAULT;
}
break;
case 'q':{
__le64 val = cpu_to_le64(va_arg(ap, int64_t));
if (pdu_write(pdu, &val, sizeof(val)))
errcode = -EFAULT;
}
break;
case 's':{
const char *sptr = va_arg(ap, const char *);
uint16_t len = 0;
if (sptr)
len = min_t(size_t, strlen(sptr),
USHRT_MAX);
errcode = p9pdu_writef(pdu, proto_version,
"w", len);
if (!errcode && pdu_write(pdu, sptr, len))
errcode = -EFAULT;
}
break;
case 'u': {
kuid_t uid = va_arg(ap, kuid_t);
__le32 val = cpu_to_le32(
from_kuid(&init_user_ns, uid));
if (pdu_write(pdu, &val, sizeof(val)))
errcode = -EFAULT;
} break;
case 'g': {
kgid_t gid = va_arg(ap, kgid_t);
__le32 val = cpu_to_le32(
from_kgid(&init_user_ns, gid));
if (pdu_write(pdu, &val, sizeof(val)))
errcode = -EFAULT;
} break;
case 'Q':{
const struct p9_qid *qid =
va_arg(ap, const struct p9_qid *);
errcode =
p9pdu_writef(pdu, proto_version, "bdq",
qid->type, qid->version,
qid->path);
} break;
case 'S':{
const struct p9_wstat *stbuf =
va_arg(ap, const struct p9_wstat *);
errcode =
p9pdu_writef(pdu, proto_version,
"wwdQdddqssss?sugu",
stbuf->size, stbuf->type,
stbuf->dev, &stbuf->qid,
stbuf->mode, stbuf->atime,
stbuf->mtime, stbuf->length,
stbuf->name, stbuf->uid,
stbuf->gid, stbuf->muid,
stbuf->extension, stbuf->n_uid,
stbuf->n_gid, stbuf->n_muid);
} break;
case 'V':{
uint32_t count = va_arg(ap, uint32_t);
struct iov_iter *from =
va_arg(ap, struct iov_iter *);
errcode = p9pdu_writef(pdu, proto_version, "d",
count);
if (!errcode && pdu_write_u(pdu, from, count))
errcode = -EFAULT;
}
break;
case 'T':{
uint16_t nwname = va_arg(ap, int);
const char **wnames = va_arg(ap, const char **);
errcode = p9pdu_writef(pdu, proto_version, "w",
nwname);
if (!errcode) {
int i;
for (i = 0; i < nwname; i++) {
errcode =
p9pdu_writef(pdu,
proto_version,
"s",
wnames[i]);
if (errcode)
break;
}
}
}
break;
case 'R':{
uint16_t nwqid = va_arg(ap, int);
struct p9_qid *wqids =
va_arg(ap, struct p9_qid *);
errcode = p9pdu_writef(pdu, proto_version, "w",
nwqid);
if (!errcode) {
int i;
for (i = 0; i < nwqid; i++) {
errcode =
p9pdu_writef(pdu,
proto_version,
"Q",
&wqids[i]);
if (errcode)
break;
}
}
}
break;
case 'I':{
struct p9_iattr_dotl *p9attr = va_arg(ap,
struct p9_iattr_dotl *);
errcode = p9pdu_writef(pdu, proto_version,
"ddugqqqqq",
p9attr->valid,
p9attr->mode,
p9attr->uid,
p9attr->gid,
p9attr->size,
p9attr->atime_sec,
p9attr->atime_nsec,
p9attr->mtime_sec,
p9attr->mtime_nsec);
}
break;
case '?':
if ((proto_version != p9_proto_2000u) &&
(proto_version != p9_proto_2000L))
return 0;
break;
default:
BUG();
break;
}
if (errcode)
break;
}
return errcode;
}
int p9pdu_readf(struct p9_fcall *pdu, int proto_version, const char *fmt, ...)
{
va_list ap;
int ret;
va_start(ap, fmt);
ret = p9pdu_vreadf(pdu, proto_version, fmt, ap);
va_end(ap);
return ret;
}
static int
p9pdu_writef(struct p9_fcall *pdu, int proto_version, const char *fmt, ...)
{
va_list ap;
int ret;
va_start(ap, fmt);
ret = p9pdu_vwritef(pdu, proto_version, fmt, ap);
va_end(ap);
return ret;
}
int p9stat_read(struct p9_client *clnt, char *buf, int len, struct p9_wstat *st)
{
struct p9_fcall fake_pdu;
int ret;
fake_pdu.size = len;
fake_pdu.capacity = len;
fake_pdu.sdata = buf;
fake_pdu.offset = 0;
ret = p9pdu_readf(&fake_pdu, clnt->proto_version, "S", st);
if (ret) {
p9_debug(P9_DEBUG_9P, "<<< p9stat_read failed: %d\n", ret);
trace_9p_protocol_dump(clnt, &fake_pdu);
return ret;
}
return fake_pdu.offset;
}
EXPORT_SYMBOL(p9stat_read);
int p9pdu_prepare(struct p9_fcall *pdu, int16_t tag, int8_t type)
{
pdu->id = type;
return p9pdu_writef(pdu, 0, "dbw", 0, type, tag);
}
int p9pdu_finalize(struct p9_client *clnt, struct p9_fcall *pdu)
{
int size = pdu->size;
int err;
pdu->size = 0;
err = p9pdu_writef(pdu, 0, "d", size);
pdu->size = size;
trace_9p_protocol_dump(clnt, pdu);
p9_debug(P9_DEBUG_9P, ">>> size=%d type: %d tag: %d\n",
pdu->size, pdu->id, pdu->tag);
return err;
}
void p9pdu_reset(struct p9_fcall *pdu)
{
pdu->offset = 0;
pdu->size = 0;
}
int p9dirent_read(struct p9_client *clnt, char *buf, int len,
struct p9_dirent *dirent)
{
struct p9_fcall fake_pdu;
int ret;
char *nameptr;
fake_pdu.size = len;
fake_pdu.capacity = len;
fake_pdu.sdata = buf;
fake_pdu.offset = 0;
ret = p9pdu_readf(&fake_pdu, clnt->proto_version, "Qqbs", &dirent->qid,
&dirent->d_off, &dirent->d_type, &nameptr);
if (ret) {
p9_debug(P9_DEBUG_9P, "<<< p9dirent_read failed: %d\n", ret);
trace_9p_protocol_dump(clnt, &fake_pdu);
goto out;
}
strcpy(dirent->d_name, nameptr);
kfree(nameptr);
out:
return fake_pdu.offset;
}
EXPORT_SYMBOL(p9dirent_read);

View File

@@ -0,0 +1,34 @@
/*
* net/9p/protocol.h
*
* 9P Protocol Support Code
*
* Copyright (C) 2008 by Eric Van Hensbergen <ericvh@gmail.com>
*
* Base on code from Anthony Liguori <aliguori@us.ibm.com>
* Copyright (C) 2008 by IBM, Corp.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
int p9pdu_vwritef(struct p9_fcall *pdu, int proto_version, const char *fmt,
va_list ap);
int p9pdu_readf(struct p9_fcall *pdu, int proto_version, const char *fmt, ...);
int p9pdu_prepare(struct p9_fcall *pdu, int16_t tag, int8_t type);
int p9pdu_finalize(struct p9_client *clnt, struct p9_fcall *pdu);
void p9pdu_reset(struct p9_fcall *pdu);
size_t pdu_read(struct p9_fcall *pdu, void *data, size_t size);

View File

@@ -0,0 +1,29 @@
/*
* Copyright IBM Corporation, 2010
* Author Venkateswararao Jujjuri <jvrao@linux.vnet.ibm.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2.1 of the GNU Lesser General Public License
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it would be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
*/
#include <linux/mm.h>
#include <linux/module.h>
/**
* p9_release_req_pages - Release pages after the transaction.
*/
void p9_release_pages(struct page **pages, int nr_pages)
{
int i;
for (i = 0; i < nr_pages; i++)
if (pages[i])
put_page(pages[i]);
}
EXPORT_SYMBOL(p9_release_pages);

View File

@@ -0,0 +1,15 @@
/*
* Copyright IBM Corporation, 2010
* Author Venkateswararao Jujjuri <jvrao@linux.vnet.ibm.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2.1 of the GNU Lesser General Public License
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it would be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
*/
void p9_release_pages(struct page **, int);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,784 @@
/*
* linux/fs/9p/trans_rdma.c
*
* RDMA transport layer based on the trans_fd.c implementation.
*
* Copyright (C) 2008 by Tom Tucker <tom@opengridcomputing.com>
* Copyright (C) 2006 by Russ Cox <rsc@swtch.com>
* Copyright (C) 2004-2005 by Latchesar Ionkov <lucho@ionkov.net>
* Copyright (C) 2004-2008 by Eric Van Hensbergen <ericvh@gmail.com>
* Copyright (C) 1997-2002 by Ron Minnich <rminnich@sarnoff.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/in.h>
#include <linux/module.h>
#include <linux/net.h>
#include <linux/ipv6.h>
#include <linux/kthread.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/un.h>
#include <linux/uaccess.h>
#include <linux/inet.h>
#include <linux/idr.h>
#include <linux/file.h>
#include <linux/parser.h>
#include <linux/semaphore.h>
#include <linux/slab.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
#include <net/9p/transport.h>
#include <rdma/ib_verbs.h>
#include <rdma/rdma_cm.h>
#define P9_PORT 5640
#define P9_RDMA_SQ_DEPTH 32
#define P9_RDMA_RQ_DEPTH 32
#define P9_RDMA_SEND_SGE 4
#define P9_RDMA_RECV_SGE 4
#define P9_RDMA_IRD 0
#define P9_RDMA_ORD 0
#define P9_RDMA_TIMEOUT 30000 /* 30 seconds */
#define P9_RDMA_MAXSIZE (1024*1024) /* 1MB */
/**
* struct p9_trans_rdma - RDMA transport instance
*
* @state: tracks the transport state machine for connection setup and tear down
* @cm_id: The RDMA CM ID
* @pd: Protection Domain pointer
* @qp: Queue Pair pointer
* @cq: Completion Queue pointer
* @dm_mr: DMA Memory Region pointer
* @lkey: The local access only memory region key
* @timeout: Number of uSecs to wait for connection management events
* @sq_depth: The depth of the Send Queue
* @sq_sem: Semaphore for the SQ
* @rq_depth: The depth of the Receive Queue.
* @rq_sem: Semaphore for the RQ
* @excess_rc : Amount of posted Receive Contexts without a pending request.
* See rdma_request()
* @addr: The remote peer's address
* @req_lock: Protects the active request list
* @cm_done: Completion event for connection management tracking
*/
struct p9_trans_rdma {
enum {
P9_RDMA_INIT,
P9_RDMA_ADDR_RESOLVED,
P9_RDMA_ROUTE_RESOLVED,
P9_RDMA_CONNECTED,
P9_RDMA_FLUSHING,
P9_RDMA_CLOSING,
P9_RDMA_CLOSED,
} state;
struct rdma_cm_id *cm_id;
struct ib_pd *pd;
struct ib_qp *qp;
struct ib_cq *cq;
long timeout;
int sq_depth;
struct semaphore sq_sem;
int rq_depth;
struct semaphore rq_sem;
atomic_t excess_rc;
struct sockaddr_in addr;
spinlock_t req_lock;
struct completion cm_done;
};
/**
* p9_rdma_context - Keeps track of in-process WR
*
* @wc_op: The original WR op for when the CQE completes in error.
* @busa: Bus address to unmap when the WR completes
* @req: Keeps track of requests (send)
* @rc: Keepts track of replies (receive)
*/
struct p9_rdma_req;
struct p9_rdma_context {
enum ib_wc_opcode wc_op;
dma_addr_t busa;
union {
struct p9_req_t *req;
struct p9_fcall *rc;
};
};
/**
* p9_rdma_opts - Collection of mount options
* @port: port of connection
* @sq_depth: The requested depth of the SQ. This really doesn't need
* to be any deeper than the number of threads used in the client
* @rq_depth: The depth of the RQ. Should be greater than or equal to SQ depth
* @timeout: Time to wait in msecs for CM events
*/
struct p9_rdma_opts {
short port;
int sq_depth;
int rq_depth;
long timeout;
int privport;
};
/*
* Option Parsing (code inspired by NFS code)
*/
enum {
/* Options that take integer arguments */
Opt_port, Opt_rq_depth, Opt_sq_depth, Opt_timeout,
/* Options that take no argument */
Opt_privport,
Opt_err,
};
static match_table_t tokens = {
{Opt_port, "port=%u"},
{Opt_sq_depth, "sq=%u"},
{Opt_rq_depth, "rq=%u"},
{Opt_timeout, "timeout=%u"},
{Opt_privport, "privport"},
{Opt_err, NULL},
};
/**
* parse_opts - parse mount options into rdma options structure
* @params: options string passed from mount
* @opts: rdma transport-specific structure to parse options into
*
* Returns 0 upon success, -ERRNO upon failure
*/
static int parse_opts(char *params, struct p9_rdma_opts *opts)
{
char *p;
substring_t args[MAX_OPT_ARGS];
int option;
char *options, *tmp_options;
opts->port = P9_PORT;
opts->sq_depth = P9_RDMA_SQ_DEPTH;
opts->rq_depth = P9_RDMA_RQ_DEPTH;
opts->timeout = P9_RDMA_TIMEOUT;
opts->privport = 0;
if (!params)
return 0;
tmp_options = kstrdup(params, GFP_KERNEL);
if (!tmp_options) {
p9_debug(P9_DEBUG_ERROR,
"failed to allocate copy of option string\n");
return -ENOMEM;
}
options = tmp_options;
while ((p = strsep(&options, ",")) != NULL) {
int token;
int r;
if (!*p)
continue;
token = match_token(p, tokens, args);
if ((token != Opt_err) && (token != Opt_privport)) {
r = match_int(&args[0], &option);
if (r < 0) {
p9_debug(P9_DEBUG_ERROR,
"integer field, but no integer?\n");
continue;
}
}
switch (token) {
case Opt_port:
opts->port = option;
break;
case Opt_sq_depth:
opts->sq_depth = option;
break;
case Opt_rq_depth:
opts->rq_depth = option;
break;
case Opt_timeout:
opts->timeout = option;
break;
case Opt_privport:
opts->privport = 1;
break;
default:
continue;
}
}
/* RQ must be at least as large as the SQ */
opts->rq_depth = max(opts->rq_depth, opts->sq_depth);
kfree(tmp_options);
return 0;
}
static int
p9_cm_event_handler(struct rdma_cm_id *id, struct rdma_cm_event *event)
{
struct p9_client *c = id->context;
struct p9_trans_rdma *rdma = c->trans;
switch (event->event) {
case RDMA_CM_EVENT_ADDR_RESOLVED:
BUG_ON(rdma->state != P9_RDMA_INIT);
rdma->state = P9_RDMA_ADDR_RESOLVED;
break;
case RDMA_CM_EVENT_ROUTE_RESOLVED:
BUG_ON(rdma->state != P9_RDMA_ADDR_RESOLVED);
rdma->state = P9_RDMA_ROUTE_RESOLVED;
break;
case RDMA_CM_EVENT_ESTABLISHED:
BUG_ON(rdma->state != P9_RDMA_ROUTE_RESOLVED);
rdma->state = P9_RDMA_CONNECTED;
break;
case RDMA_CM_EVENT_DISCONNECTED:
if (rdma)
rdma->state = P9_RDMA_CLOSED;
if (c)
c->status = Disconnected;
break;
case RDMA_CM_EVENT_TIMEWAIT_EXIT:
break;
case RDMA_CM_EVENT_ADDR_CHANGE:
case RDMA_CM_EVENT_ROUTE_ERROR:
case RDMA_CM_EVENT_DEVICE_REMOVAL:
case RDMA_CM_EVENT_MULTICAST_JOIN:
case RDMA_CM_EVENT_MULTICAST_ERROR:
case RDMA_CM_EVENT_REJECTED:
case RDMA_CM_EVENT_CONNECT_REQUEST:
case RDMA_CM_EVENT_CONNECT_RESPONSE:
case RDMA_CM_EVENT_CONNECT_ERROR:
case RDMA_CM_EVENT_ADDR_ERROR:
case RDMA_CM_EVENT_UNREACHABLE:
c->status = Disconnected;
rdma_disconnect(rdma->cm_id);
break;
default:
BUG();
}
complete(&rdma->cm_done);
return 0;
}
static void
handle_recv(struct p9_client *client, struct p9_trans_rdma *rdma,
struct p9_rdma_context *c, enum ib_wc_status status, u32 byte_len)
{
struct p9_req_t *req;
int err = 0;
int16_t tag;
req = NULL;
ib_dma_unmap_single(rdma->cm_id->device, c->busa, client->msize,
DMA_FROM_DEVICE);
if (status != IB_WC_SUCCESS)
goto err_out;
err = p9_parse_header(c->rc, NULL, NULL, &tag, 1);
if (err)
goto err_out;
req = p9_tag_lookup(client, tag);
if (!req)
goto err_out;
/* Check that we have not yet received a reply for this request.
*/
if (unlikely(req->rc)) {
pr_err("Duplicate reply for request %d", tag);
goto err_out;
}
req->rc = c->rc;
p9_client_cb(client, req, REQ_STATUS_RCVD);
return;
err_out:
p9_debug(P9_DEBUG_ERROR, "req %p err %d status %d\n", req, err, status);
rdma->state = P9_RDMA_FLUSHING;
client->status = Disconnected;
}
static void
handle_send(struct p9_client *client, struct p9_trans_rdma *rdma,
struct p9_rdma_context *c, enum ib_wc_status status, u32 byte_len)
{
ib_dma_unmap_single(rdma->cm_id->device,
c->busa, c->req->tc->size,
DMA_TO_DEVICE);
}
static void qp_event_handler(struct ib_event *event, void *context)
{
p9_debug(P9_DEBUG_ERROR, "QP event %d context %p\n",
event->event, context);
}
static void cq_comp_handler(struct ib_cq *cq, void *cq_context)
{
struct p9_client *client = cq_context;
struct p9_trans_rdma *rdma = client->trans;
int ret;
struct ib_wc wc;
ib_req_notify_cq(rdma->cq, IB_CQ_NEXT_COMP);
while ((ret = ib_poll_cq(cq, 1, &wc)) > 0) {
struct p9_rdma_context *c = (void *) (unsigned long) wc.wr_id;
switch (c->wc_op) {
case IB_WC_RECV:
handle_recv(client, rdma, c, wc.status, wc.byte_len);
up(&rdma->rq_sem);
break;
case IB_WC_SEND:
handle_send(client, rdma, c, wc.status, wc.byte_len);
up(&rdma->sq_sem);
break;
default:
pr_err("unexpected completion type, c->wc_op=%d, wc.opcode=%d, status=%d\n",
c->wc_op, wc.opcode, wc.status);
break;
}
kfree(c);
}
}
static void cq_event_handler(struct ib_event *e, void *v)
{
p9_debug(P9_DEBUG_ERROR, "CQ event %d context %p\n", e->event, v);
}
static void rdma_destroy_trans(struct p9_trans_rdma *rdma)
{
if (!rdma)
return;
if (rdma->qp && !IS_ERR(rdma->qp))
ib_destroy_qp(rdma->qp);
if (rdma->pd && !IS_ERR(rdma->pd))
ib_dealloc_pd(rdma->pd);
if (rdma->cq && !IS_ERR(rdma->cq))
ib_destroy_cq(rdma->cq);
if (rdma->cm_id && !IS_ERR(rdma->cm_id))
rdma_destroy_id(rdma->cm_id);
kfree(rdma);
}
static int
post_recv(struct p9_client *client, struct p9_rdma_context *c)
{
struct p9_trans_rdma *rdma = client->trans;
struct ib_recv_wr wr, *bad_wr;
struct ib_sge sge;
c->busa = ib_dma_map_single(rdma->cm_id->device,
c->rc->sdata, client->msize,
DMA_FROM_DEVICE);
if (ib_dma_mapping_error(rdma->cm_id->device, c->busa))
goto error;
sge.addr = c->busa;
sge.length = client->msize;
sge.lkey = rdma->pd->local_dma_lkey;
wr.next = NULL;
c->wc_op = IB_WC_RECV;
wr.wr_id = (unsigned long) c;
wr.sg_list = &sge;
wr.num_sge = 1;
return ib_post_recv(rdma->qp, &wr, &bad_wr);
error:
p9_debug(P9_DEBUG_ERROR, "EIO\n");
return -EIO;
}
static int rdma_request(struct p9_client *client, struct p9_req_t *req)
{
struct p9_trans_rdma *rdma = client->trans;
struct ib_send_wr wr, *bad_wr;
struct ib_sge sge;
int err = 0;
unsigned long flags;
struct p9_rdma_context *c = NULL;
struct p9_rdma_context *rpl_context = NULL;
/* When an error occurs between posting the recv and the send,
* there will be a receive context posted without a pending request.
* Since there is no way to "un-post" it, we remember it and skip
* post_recv() for the next request.
* So here,
* see if we are this `next request' and need to absorb an excess rc.
* If yes, then drop and free our own, and do not recv_post().
**/
if (unlikely(atomic_read(&rdma->excess_rc) > 0)) {
if ((atomic_sub_return(1, &rdma->excess_rc) >= 0)) {
/* Got one ! */
kfree(req->rc);
req->rc = NULL;
goto dont_need_post_recv;
} else {
/* We raced and lost. */
atomic_inc(&rdma->excess_rc);
}
}
/* Allocate an fcall for the reply */
rpl_context = kmalloc(sizeof *rpl_context, GFP_NOFS);
if (!rpl_context) {
err = -ENOMEM;
goto recv_error;
}
rpl_context->rc = req->rc;
/*
* Post a receive buffer for this request. We need to ensure
* there is a reply buffer available for every outstanding
* request. A flushed request can result in no reply for an
* outstanding request, so we must keep a count to avoid
* overflowing the RQ.
*/
if (down_interruptible(&rdma->rq_sem)) {
err = -EINTR;
goto recv_error;
}
err = post_recv(client, rpl_context);
if (err) {
p9_debug(P9_DEBUG_FCALL, "POST RECV failed\n");
goto recv_error;
}
/* remove posted receive buffer from request structure */
req->rc = NULL;
dont_need_post_recv:
/* Post the request */
c = kmalloc(sizeof *c, GFP_NOFS);
if (!c) {
err = -ENOMEM;
goto send_error;
}
c->req = req;
c->busa = ib_dma_map_single(rdma->cm_id->device,
c->req->tc->sdata, c->req->tc->size,
DMA_TO_DEVICE);
if (ib_dma_mapping_error(rdma->cm_id->device, c->busa)) {
err = -EIO;
goto send_error;
}
sge.addr = c->busa;
sge.length = c->req->tc->size;
sge.lkey = rdma->pd->local_dma_lkey;
wr.next = NULL;
c->wc_op = IB_WC_SEND;
wr.wr_id = (unsigned long) c;
wr.opcode = IB_WR_SEND;
wr.send_flags = IB_SEND_SIGNALED;
wr.sg_list = &sge;
wr.num_sge = 1;
if (down_interruptible(&rdma->sq_sem)) {
err = -EINTR;
goto send_error;
}
/* Mark request as `sent' *before* we actually send it,
* because doing if after could erase the REQ_STATUS_RCVD
* status in case of a very fast reply.
*/
req->status = REQ_STATUS_SENT;
err = ib_post_send(rdma->qp, &wr, &bad_wr);
if (err)
goto send_error;
/* Success */
return 0;
/* Handle errors that happened during or while preparing the send: */
send_error:
req->status = REQ_STATUS_ERROR;
kfree(c);
p9_debug(P9_DEBUG_ERROR, "Error %d in rdma_request()\n", err);
/* Ach.
* We did recv_post(), but not send. We have one recv_post in excess.
*/
atomic_inc(&rdma->excess_rc);
return err;
/* Handle errors that happened during or while preparing post_recv(): */
recv_error:
kfree(rpl_context);
spin_lock_irqsave(&rdma->req_lock, flags);
if (rdma->state < P9_RDMA_CLOSING) {
rdma->state = P9_RDMA_CLOSING;
spin_unlock_irqrestore(&rdma->req_lock, flags);
rdma_disconnect(rdma->cm_id);
} else
spin_unlock_irqrestore(&rdma->req_lock, flags);
return err;
}
static void rdma_close(struct p9_client *client)
{
struct p9_trans_rdma *rdma;
if (!client)
return;
rdma = client->trans;
if (!rdma)
return;
client->status = Disconnected;
rdma_disconnect(rdma->cm_id);
rdma_destroy_trans(rdma);
}
/**
* alloc_rdma - Allocate and initialize the rdma transport structure
* @opts: Mount options structure
*/
static struct p9_trans_rdma *alloc_rdma(struct p9_rdma_opts *opts)
{
struct p9_trans_rdma *rdma;
rdma = kzalloc(sizeof(struct p9_trans_rdma), GFP_KERNEL);
if (!rdma)
return NULL;
rdma->sq_depth = opts->sq_depth;
rdma->rq_depth = opts->rq_depth;
rdma->timeout = opts->timeout;
spin_lock_init(&rdma->req_lock);
init_completion(&rdma->cm_done);
sema_init(&rdma->sq_sem, rdma->sq_depth);
sema_init(&rdma->rq_sem, rdma->rq_depth);
atomic_set(&rdma->excess_rc, 0);
return rdma;
}
static int rdma_cancel(struct p9_client *client, struct p9_req_t *req)
{
/* Nothing to do here.
* We will take care of it (if we have to) in rdma_cancelled()
*/
return 1;
}
/* A request has been fully flushed without a reply.
* That means we have posted one buffer in excess.
*/
static int rdma_cancelled(struct p9_client *client, struct p9_req_t *req)
{
struct p9_trans_rdma *rdma = client->trans;
atomic_inc(&rdma->excess_rc);
return 0;
}
static int p9_rdma_bind_privport(struct p9_trans_rdma *rdma)
{
struct sockaddr_in cl = {
.sin_family = AF_INET,
.sin_addr.s_addr = htonl(INADDR_ANY),
};
int port, err = -EINVAL;
for (port = P9_DEF_MAX_RESVPORT; port >= P9_DEF_MIN_RESVPORT; port--) {
cl.sin_port = htons((ushort)port);
err = rdma_bind_addr(rdma->cm_id, (struct sockaddr *)&cl);
if (err != -EADDRINUSE)
break;
}
return err;
}
/**
* trans_create_rdma - Transport method for creating atransport instance
* @client: client instance
* @addr: IP address string
* @args: Mount options string
*/
static int
rdma_create_trans(struct p9_client *client, const char *addr, char *args)
{
int err;
struct p9_rdma_opts opts;
struct p9_trans_rdma *rdma;
struct rdma_conn_param conn_param;
struct ib_qp_init_attr qp_attr;
struct ib_cq_init_attr cq_attr = {};
if (addr == NULL)
return -EINVAL;
/* Parse the transport specific mount options */
err = parse_opts(args, &opts);
if (err < 0)
return err;
/* Create and initialize the RDMA transport structure */
rdma = alloc_rdma(&opts);
if (!rdma)
return -ENOMEM;
/* Create the RDMA CM ID */
rdma->cm_id = rdma_create_id(&init_net, p9_cm_event_handler, client,
RDMA_PS_TCP, IB_QPT_RC);
if (IS_ERR(rdma->cm_id))
goto error;
/* Associate the client with the transport */
client->trans = rdma;
/* Bind to a privileged port if we need to */
if (opts.privport) {
err = p9_rdma_bind_privport(rdma);
if (err < 0) {
pr_err("%s (%d): problem binding to privport: %d\n",
__func__, task_pid_nr(current), -err);
goto error;
}
}
/* Resolve the server's address */
rdma->addr.sin_family = AF_INET;
rdma->addr.sin_addr.s_addr = in_aton(addr);
rdma->addr.sin_port = htons(opts.port);
err = rdma_resolve_addr(rdma->cm_id, NULL,
(struct sockaddr *)&rdma->addr,
rdma->timeout);
if (err)
goto error;
err = wait_for_completion_interruptible(&rdma->cm_done);
if (err || (rdma->state != P9_RDMA_ADDR_RESOLVED))
goto error;
/* Resolve the route to the server */
err = rdma_resolve_route(rdma->cm_id, rdma->timeout);
if (err)
goto error;
err = wait_for_completion_interruptible(&rdma->cm_done);
if (err || (rdma->state != P9_RDMA_ROUTE_RESOLVED))
goto error;
/* Create the Completion Queue */
cq_attr.cqe = opts.sq_depth + opts.rq_depth + 1;
rdma->cq = ib_create_cq(rdma->cm_id->device, cq_comp_handler,
cq_event_handler, client,
&cq_attr);
if (IS_ERR(rdma->cq))
goto error;
ib_req_notify_cq(rdma->cq, IB_CQ_NEXT_COMP);
/* Create the Protection Domain */
rdma->pd = ib_alloc_pd(rdma->cm_id->device);
if (IS_ERR(rdma->pd))
goto error;
/* Create the Queue Pair */
memset(&qp_attr, 0, sizeof qp_attr);
qp_attr.event_handler = qp_event_handler;
qp_attr.qp_context = client;
qp_attr.cap.max_send_wr = opts.sq_depth;
qp_attr.cap.max_recv_wr = opts.rq_depth;
qp_attr.cap.max_send_sge = P9_RDMA_SEND_SGE;
qp_attr.cap.max_recv_sge = P9_RDMA_RECV_SGE;
qp_attr.sq_sig_type = IB_SIGNAL_REQ_WR;
qp_attr.qp_type = IB_QPT_RC;
qp_attr.send_cq = rdma->cq;
qp_attr.recv_cq = rdma->cq;
err = rdma_create_qp(rdma->cm_id, rdma->pd, &qp_attr);
if (err)
goto error;
rdma->qp = rdma->cm_id->qp;
/* Request a connection */
memset(&conn_param, 0, sizeof(conn_param));
conn_param.private_data = NULL;
conn_param.private_data_len = 0;
conn_param.responder_resources = P9_RDMA_IRD;
conn_param.initiator_depth = P9_RDMA_ORD;
err = rdma_connect(rdma->cm_id, &conn_param);
if (err)
goto error;
err = wait_for_completion_interruptible(&rdma->cm_done);
if (err || (rdma->state != P9_RDMA_CONNECTED))
goto error;
client->status = Connected;
return 0;
error:
rdma_destroy_trans(rdma);
return -ENOTCONN;
}
static struct p9_trans_module p9_rdma_trans = {
.name = "rdma",
.maxsize = P9_RDMA_MAXSIZE,
.def = 0,
.owner = THIS_MODULE,
.create = rdma_create_trans,
.close = rdma_close,
.request = rdma_request,
.cancel = rdma_cancel,
.cancelled = rdma_cancelled,
};
/**
* p9_trans_rdma_init - Register the 9P RDMA transport driver
*/
static int __init p9_trans_rdma_init(void)
{
v9fs_register_trans(&p9_rdma_trans);
return 0;
}
static void __exit p9_trans_rdma_exit(void)
{
v9fs_unregister_trans(&p9_rdma_trans);
}
module_init(p9_trans_rdma_init);
module_exit(p9_trans_rdma_exit);
MODULE_AUTHOR("Tom Tucker <tom@opengridcomputing.com>");
MODULE_DESCRIPTION("RDMA Transport for 9P");
MODULE_LICENSE("Dual BSD/GPL");

View File

@@ -0,0 +1,788 @@
/*
* The Virtio 9p transport driver
*
* This is a block based transport driver based on the lguest block driver
* code.
*
* Copyright (C) 2007, 2008 Eric Van Hensbergen, IBM Corporation
*
* Based on virtio console driver
* Copyright (C) 2006, 2007 Rusty Russell, IBM Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/in.h>
#include <linux/module.h>
#include <linux/net.h>
#include <linux/ipv6.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/un.h>
#include <linux/uaccess.h>
#include <linux/inet.h>
#include <linux/idr.h>
#include <linux/file.h>
#include <linux/highmem.h>
#include <linux/slab.h>
#include <net/9p/9p.h>
#include <linux/parser.h>
#include <net/9p/client.h>
#include <net/9p/transport.h>
#include <linux/scatterlist.h>
#include <linux/swap.h>
#include <linux/virtio.h>
#include <linux/virtio_9p.h>
#include "trans_common.h"
#define VIRTQUEUE_NUM 128
/* a single mutex to manage channel initialization and attachment */
static DEFINE_MUTEX(virtio_9p_lock);
static DECLARE_WAIT_QUEUE_HEAD(vp_wq);
static atomic_t vp_pinned = ATOMIC_INIT(0);
/**
* struct virtio_chan - per-instance transport information
* @initialized: whether the channel is initialized
* @inuse: whether the channel is in use
* @lock: protects multiple elements within this structure
* @client: client instance
* @vdev: virtio dev associated with this channel
* @vq: virtio queue associated with this channel
* @sg: scatter gather list which is used to pack a request (protected?)
*
* We keep all per-channel information in a structure.
* This structure is allocated within the devices dev->mem space.
* A pointer to the structure will get put in the transport private.
*
*/
struct virtio_chan {
bool inuse;
spinlock_t lock;
struct p9_client *client;
struct virtio_device *vdev;
struct virtqueue *vq;
int ring_bufs_avail;
wait_queue_head_t *vc_wq;
/* This is global limit. Since we don't have a global structure,
* will be placing it in each channel.
*/
unsigned long p9_max_pages;
/* Scatterlist: can be too big for stack. */
struct scatterlist sg[VIRTQUEUE_NUM];
int tag_len;
/*
* tag name to identify a mount Non-null terminated
*/
char *tag;
struct list_head chan_list;
};
static struct list_head virtio_chan_list;
/* How many bytes left in this page. */
static unsigned int rest_of_page(void *data)
{
return PAGE_SIZE - ((unsigned long)data % PAGE_SIZE);
}
/**
* p9_virtio_close - reclaim resources of a channel
* @client: client instance
*
* This reclaims a channel by freeing its resources and
* reseting its inuse flag.
*
*/
static void p9_virtio_close(struct p9_client *client)
{
struct virtio_chan *chan = client->trans;
mutex_lock(&virtio_9p_lock);
if (chan)
chan->inuse = false;
mutex_unlock(&virtio_9p_lock);
}
/**
* req_done - callback which signals activity from the server
* @vq: virtio queue activity was received on
*
* This notifies us that the server has triggered some activity
* on the virtio channel - most likely a response to request we
* sent. Figure out which requests now have responses and wake up
* those threads.
*
* Bugs: could do with some additional sanity checking, but appears to work.
*
*/
static void req_done(struct virtqueue *vq)
{
struct virtio_chan *chan = vq->vdev->priv;
struct p9_fcall *rc;
unsigned int len;
struct p9_req_t *req;
unsigned long flags;
p9_debug(P9_DEBUG_TRANS, ": request done\n");
while (1) {
spin_lock_irqsave(&chan->lock, flags);
rc = virtqueue_get_buf(chan->vq, &len);
if (rc == NULL) {
spin_unlock_irqrestore(&chan->lock, flags);
break;
}
chan->ring_bufs_avail = 1;
spin_unlock_irqrestore(&chan->lock, flags);
/* Wakeup if anyone waiting for VirtIO ring space. */
wake_up(chan->vc_wq);
p9_debug(P9_DEBUG_TRANS, ": rc %p\n", rc);
p9_debug(P9_DEBUG_TRANS, ": lookup tag %d\n", rc->tag);
req = p9_tag_lookup(chan->client, rc->tag);
p9_client_cb(chan->client, req, REQ_STATUS_RCVD);
}
}
/**
* pack_sg_list - pack a scatter gather list from a linear buffer
* @sg: scatter/gather list to pack into
* @start: which segment of the sg_list to start at
* @limit: maximum segment to pack data to
* @data: data to pack into scatter/gather list
* @count: amount of data to pack into the scatter/gather list
*
* sg_lists have multiple segments of various sizes. This will pack
* arbitrary data into an existing scatter gather list, segmenting the
* data as necessary within constraints.
*
*/
static int pack_sg_list(struct scatterlist *sg, int start,
int limit, char *data, int count)
{
int s;
int index = start;
while (count) {
s = rest_of_page(data);
if (s > count)
s = count;
BUG_ON(index >= limit);
/* Make sure we don't terminate early. */
sg_unmark_end(&sg[index]);
sg_set_buf(&sg[index++], data, s);
count -= s;
data += s;
}
if (index-start)
sg_mark_end(&sg[index - 1]);
return index-start;
}
/* We don't currently allow canceling of virtio requests */
static int p9_virtio_cancel(struct p9_client *client, struct p9_req_t *req)
{
return 1;
}
/**
* pack_sg_list_p - Just like pack_sg_list. Instead of taking a buffer,
* this takes a list of pages.
* @sg: scatter/gather list to pack into
* @start: which segment of the sg_list to start at
* @pdata: a list of pages to add into sg.
* @nr_pages: number of pages to pack into the scatter/gather list
* @offs: amount of data in the beginning of first page _not_ to pack
* @count: amount of data to pack into the scatter/gather list
*/
static int
pack_sg_list_p(struct scatterlist *sg, int start, int limit,
struct page **pdata, int nr_pages, size_t offs, int count)
{
int i = 0, s;
int data_off = offs;
int index = start;
BUG_ON(nr_pages > (limit - start));
/*
* if the first page doesn't start at
* page boundary find the offset
*/
while (nr_pages) {
s = PAGE_SIZE - data_off;
if (s > count)
s = count;
BUG_ON(index >= limit);
/* Make sure we don't terminate early. */
sg_unmark_end(&sg[index]);
sg_set_page(&sg[index++], pdata[i++], s, data_off);
data_off = 0;
count -= s;
nr_pages--;
}
if (index-start)
sg_mark_end(&sg[index - 1]);
return index - start;
}
/**
* p9_virtio_request - issue a request
* @client: client instance issuing the request
* @req: request to be issued
*
*/
static int
p9_virtio_request(struct p9_client *client, struct p9_req_t *req)
{
int err;
int in, out, out_sgs, in_sgs;
unsigned long flags;
struct virtio_chan *chan = client->trans;
struct scatterlist *sgs[2];
p9_debug(P9_DEBUG_TRANS, "9p debug: virtio request\n");
req->status = REQ_STATUS_SENT;
req_retry:
spin_lock_irqsave(&chan->lock, flags);
out_sgs = in_sgs = 0;
/* Handle out VirtIO ring buffers */
out = pack_sg_list(chan->sg, 0,
VIRTQUEUE_NUM, req->tc->sdata, req->tc->size);
if (out)
sgs[out_sgs++] = chan->sg;
in = pack_sg_list(chan->sg, out,
VIRTQUEUE_NUM, req->rc->sdata, req->rc->capacity);
if (in)
sgs[out_sgs + in_sgs++] = chan->sg + out;
err = virtqueue_add_sgs(chan->vq, sgs, out_sgs, in_sgs, req->tc,
GFP_ATOMIC);
if (err < 0) {
if (err == -ENOSPC) {
chan->ring_bufs_avail = 0;
spin_unlock_irqrestore(&chan->lock, flags);
err = wait_event_killable(*chan->vc_wq,
chan->ring_bufs_avail);
if (err == -ERESTARTSYS)
return err;
p9_debug(P9_DEBUG_TRANS, "Retry virtio request\n");
goto req_retry;
} else {
spin_unlock_irqrestore(&chan->lock, flags);
p9_debug(P9_DEBUG_TRANS,
"virtio rpc add_sgs returned failure\n");
return -EIO;
}
}
virtqueue_kick(chan->vq);
spin_unlock_irqrestore(&chan->lock, flags);
p9_debug(P9_DEBUG_TRANS, "virtio request kicked\n");
return 0;
}
static int p9_get_mapped_pages(struct virtio_chan *chan,
struct page ***pages,
struct iov_iter *data,
int count,
size_t *offs,
int *need_drop)
{
int nr_pages;
int err;
if (!iov_iter_count(data))
return 0;
if (!(data->type & ITER_KVEC)) {
int n;
/*
* We allow only p9_max_pages pinned. We wait for the
* Other zc request to finish here
*/
if (atomic_read(&vp_pinned) >= chan->p9_max_pages) {
err = wait_event_killable(vp_wq,
(atomic_read(&vp_pinned) < chan->p9_max_pages));
if (err == -ERESTARTSYS)
return err;
}
n = iov_iter_get_pages_alloc(data, pages, count, offs);
if (n < 0)
return n;
*need_drop = 1;
nr_pages = DIV_ROUND_UP(n + *offs, PAGE_SIZE);
atomic_add(nr_pages, &vp_pinned);
return n;
} else {
/* kernel buffer, no need to pin pages */
int index;
size_t len;
void *p;
/* we'd already checked that it's non-empty */
while (1) {
len = iov_iter_single_seg_count(data);
if (likely(len)) {
p = data->kvec->iov_base + data->iov_offset;
break;
}
iov_iter_advance(data, 0);
}
if (len > count)
len = count;
nr_pages = DIV_ROUND_UP((unsigned long)p + len, PAGE_SIZE) -
(unsigned long)p / PAGE_SIZE;
*pages = kmalloc(sizeof(struct page *) * nr_pages, GFP_NOFS);
if (!*pages)
return -ENOMEM;
*need_drop = 0;
p -= (*offs = (unsigned long)p % PAGE_SIZE);
for (index = 0; index < nr_pages; index++) {
if (is_vmalloc_addr(p))
(*pages)[index] = vmalloc_to_page(p);
else
(*pages)[index] = kmap_to_page(p);
p += PAGE_SIZE;
}
return len;
}
}
/**
* p9_virtio_zc_request - issue a zero copy request
* @client: client instance issuing the request
* @req: request to be issued
* @uidata: user bffer that should be ued for zero copy read
* @uodata: user buffer that shoud be user for zero copy write
* @inlen: read buffer size
* @olen: write buffer size
* @hdrlen: reader header size, This is the size of response protocol data
*
*/
static int
p9_virtio_zc_request(struct p9_client *client, struct p9_req_t *req,
struct iov_iter *uidata, struct iov_iter *uodata,
int inlen, int outlen, int in_hdr_len)
{
int in, out, err, out_sgs, in_sgs;
unsigned long flags;
int in_nr_pages = 0, out_nr_pages = 0;
struct page **in_pages = NULL, **out_pages = NULL;
struct virtio_chan *chan = client->trans;
struct scatterlist *sgs[4];
size_t offs;
int need_drop = 0;
p9_debug(P9_DEBUG_TRANS, "virtio request\n");
if (uodata) {
__le32 sz;
int n = p9_get_mapped_pages(chan, &out_pages, uodata,
outlen, &offs, &need_drop);
if (n < 0)
return n;
out_nr_pages = DIV_ROUND_UP(n + offs, PAGE_SIZE);
if (n != outlen) {
__le32 v = cpu_to_le32(n);
memcpy(&req->tc->sdata[req->tc->size - 4], &v, 4);
outlen = n;
}
/* The size field of the message must include the length of the
* header and the length of the data. We didn't actually know
* the length of the data until this point so add it in now.
*/
sz = cpu_to_le32(req->tc->size + outlen);
memcpy(&req->tc->sdata[0], &sz, sizeof(sz));
} else if (uidata) {
int n = p9_get_mapped_pages(chan, &in_pages, uidata,
inlen, &offs, &need_drop);
if (n < 0)
return n;
in_nr_pages = DIV_ROUND_UP(n + offs, PAGE_SIZE);
if (n != inlen) {
__le32 v = cpu_to_le32(n);
memcpy(&req->tc->sdata[req->tc->size - 4], &v, 4);
inlen = n;
}
}
req->status = REQ_STATUS_SENT;
req_retry_pinned:
spin_lock_irqsave(&chan->lock, flags);
out_sgs = in_sgs = 0;
/* out data */
out = pack_sg_list(chan->sg, 0,
VIRTQUEUE_NUM, req->tc->sdata, req->tc->size);
if (out)
sgs[out_sgs++] = chan->sg;
if (out_pages) {
sgs[out_sgs++] = chan->sg + out;
out += pack_sg_list_p(chan->sg, out, VIRTQUEUE_NUM,
out_pages, out_nr_pages, offs, outlen);
}
/*
* Take care of in data
* For example TREAD have 11.
* 11 is the read/write header = PDU Header(7) + IO Size (4).
* Arrange in such a way that server places header in the
* alloced memory and payload onto the user buffer.
*/
in = pack_sg_list(chan->sg, out,
VIRTQUEUE_NUM, req->rc->sdata, in_hdr_len);
if (in)
sgs[out_sgs + in_sgs++] = chan->sg + out;
if (in_pages) {
sgs[out_sgs + in_sgs++] = chan->sg + out + in;
in += pack_sg_list_p(chan->sg, out + in, VIRTQUEUE_NUM,
in_pages, in_nr_pages, offs, inlen);
}
BUG_ON(out_sgs + in_sgs > ARRAY_SIZE(sgs));
err = virtqueue_add_sgs(chan->vq, sgs, out_sgs, in_sgs, req->tc,
GFP_ATOMIC);
if (err < 0) {
if (err == -ENOSPC) {
chan->ring_bufs_avail = 0;
spin_unlock_irqrestore(&chan->lock, flags);
err = wait_event_killable(*chan->vc_wq,
chan->ring_bufs_avail);
if (err == -ERESTARTSYS)
goto err_out;
p9_debug(P9_DEBUG_TRANS, "Retry virtio request\n");
goto req_retry_pinned;
} else {
spin_unlock_irqrestore(&chan->lock, flags);
p9_debug(P9_DEBUG_TRANS,
"virtio rpc add_sgs returned failure\n");
err = -EIO;
goto err_out;
}
}
virtqueue_kick(chan->vq);
spin_unlock_irqrestore(&chan->lock, flags);
p9_debug(P9_DEBUG_TRANS, "virtio request kicked\n");
err = wait_event_killable(*req->wq, req->status >= REQ_STATUS_RCVD);
/*
* Non kernel buffers are pinned, unpin them
*/
err_out:
if (need_drop) {
if (in_pages) {
p9_release_pages(in_pages, in_nr_pages);
atomic_sub(in_nr_pages, &vp_pinned);
}
if (out_pages) {
p9_release_pages(out_pages, out_nr_pages);
atomic_sub(out_nr_pages, &vp_pinned);
}
/* wakeup anybody waiting for slots to pin pages */
wake_up(&vp_wq);
}
kfree(in_pages);
kfree(out_pages);
return err;
}
static ssize_t p9_mount_tag_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct virtio_chan *chan;
struct virtio_device *vdev;
vdev = dev_to_virtio(dev);
chan = vdev->priv;
memcpy(buf, chan->tag, chan->tag_len);
buf[chan->tag_len] = 0;
return chan->tag_len + 1;
}
static DEVICE_ATTR(mount_tag, 0444, p9_mount_tag_show, NULL);
/**
* p9_virtio_probe - probe for existence of 9P virtio channels
* @vdev: virtio device to probe
*
* This probes for existing virtio channels.
*
*/
static int p9_virtio_probe(struct virtio_device *vdev)
{
__u16 tag_len;
char *tag;
int err;
struct virtio_chan *chan;
if (!vdev->config->get) {
dev_err(&vdev->dev, "%s failure: config access disabled\n",
__func__);
return -EINVAL;
}
chan = kmalloc(sizeof(struct virtio_chan), GFP_KERNEL);
if (!chan) {
pr_err("Failed to allocate virtio 9P channel\n");
err = -ENOMEM;
goto fail;
}
chan->vdev = vdev;
/* We expect one virtqueue, for requests. */
chan->vq = virtio_find_single_vq(vdev, req_done, "requests");
if (IS_ERR(chan->vq)) {
err = PTR_ERR(chan->vq);
goto out_free_chan;
}
chan->vq->vdev->priv = chan;
spin_lock_init(&chan->lock);
sg_init_table(chan->sg, VIRTQUEUE_NUM);
chan->inuse = false;
if (virtio_has_feature(vdev, VIRTIO_9P_MOUNT_TAG)) {
virtio_cread(vdev, struct virtio_9p_config, tag_len, &tag_len);
} else {
err = -EINVAL;
goto out_free_vq;
}
tag = kmalloc(tag_len, GFP_KERNEL);
if (!tag) {
err = -ENOMEM;
goto out_free_vq;
}
virtio_cread_bytes(vdev, offsetof(struct virtio_9p_config, tag),
tag, tag_len);
chan->tag = tag;
chan->tag_len = tag_len;
err = sysfs_create_file(&(vdev->dev.kobj), &dev_attr_mount_tag.attr);
if (err) {
goto out_free_tag;
}
chan->vc_wq = kmalloc(sizeof(wait_queue_head_t), GFP_KERNEL);
if (!chan->vc_wq) {
err = -ENOMEM;
goto out_free_tag;
}
init_waitqueue_head(chan->vc_wq);
chan->ring_bufs_avail = 1;
/* Ceiling limit to avoid denial of service attacks */
chan->p9_max_pages = nr_free_buffer_pages()/4;
virtio_device_ready(vdev);
mutex_lock(&virtio_9p_lock);
list_add_tail(&chan->chan_list, &virtio_chan_list);
mutex_unlock(&virtio_9p_lock);
/* Let udev rules use the new mount_tag attribute. */
kobject_uevent(&(vdev->dev.kobj), KOBJ_CHANGE);
return 0;
out_free_tag:
kfree(tag);
out_free_vq:
vdev->config->del_vqs(vdev);
out_free_chan:
kfree(chan);
fail:
return err;
}
/**
* p9_virtio_create - allocate a new virtio channel
* @client: client instance invoking this transport
* @devname: string identifying the channel to connect to (unused)
* @args: args passed from sys_mount() for per-transport options (unused)
*
* This sets up a transport channel for 9p communication. Right now
* we only match the first available channel, but eventually we couldlook up
* alternate channels by matching devname versus a virtio_config entry.
* We use a simple reference count mechanism to ensure that only a single
* mount has a channel open at a time.
*
*/
static int
p9_virtio_create(struct p9_client *client, const char *devname, char *args)
{
struct virtio_chan *chan;
int ret = -ENOENT;
int found = 0;
if (devname == NULL)
return -EINVAL;
mutex_lock(&virtio_9p_lock);
list_for_each_entry(chan, &virtio_chan_list, chan_list) {
if (!strncmp(devname, chan->tag, chan->tag_len) &&
strlen(devname) == chan->tag_len) {
if (!chan->inuse) {
chan->inuse = true;
found = 1;
break;
}
ret = -EBUSY;
}
}
mutex_unlock(&virtio_9p_lock);
if (!found) {
pr_err("no channels available\n");
return ret;
}
client->trans = (void *)chan;
client->status = Connected;
chan->client = client;
return 0;
}
/**
* p9_virtio_remove - clean up resources associated with a virtio device
* @vdev: virtio device to remove
*
*/
static void p9_virtio_remove(struct virtio_device *vdev)
{
struct virtio_chan *chan = vdev->priv;
unsigned long warning_time;
mutex_lock(&virtio_9p_lock);
/* Remove self from list so we don't get new users. */
list_del(&chan->chan_list);
warning_time = jiffies;
/* Wait for existing users to close. */
while (chan->inuse) {
mutex_unlock(&virtio_9p_lock);
msleep(250);
if (time_after(jiffies, warning_time + 10 * HZ)) {
dev_emerg(&vdev->dev,
"p9_virtio_remove: waiting for device in use.\n");
warning_time = jiffies;
}
mutex_lock(&virtio_9p_lock);
}
mutex_unlock(&virtio_9p_lock);
vdev->config->reset(vdev);
vdev->config->del_vqs(vdev);
sysfs_remove_file(&(vdev->dev.kobj), &dev_attr_mount_tag.attr);
kobject_uevent(&(vdev->dev.kobj), KOBJ_CHANGE);
kfree(chan->tag);
kfree(chan->vc_wq);
kfree(chan);
}
static struct virtio_device_id id_table[] = {
{ VIRTIO_ID_9P, VIRTIO_DEV_ANY_ID },
{ 0 },
};
static unsigned int features[] = {
VIRTIO_9P_MOUNT_TAG,
};
/* The standard "struct lguest_driver": */
static struct virtio_driver p9_virtio_drv = {
.feature_table = features,
.feature_table_size = ARRAY_SIZE(features),
.driver.name = KBUILD_MODNAME,
.driver.owner = THIS_MODULE,
.id_table = id_table,
.probe = p9_virtio_probe,
.remove = p9_virtio_remove,
};
static struct p9_trans_module p9_virtio_trans = {
.name = "virtio",
.create = p9_virtio_create,
.close = p9_virtio_close,
.request = p9_virtio_request,
.zc_request = p9_virtio_zc_request,
.cancel = p9_virtio_cancel,
/*
* We leave one entry for input and one entry for response
* headers. We also skip one more entry to accomodate, address
* that are not at page boundary, that can result in an extra
* page in zero copy.
*/
.maxsize = PAGE_SIZE * (VIRTQUEUE_NUM - 3),
.def = 1,
.owner = THIS_MODULE,
};
/* The standard init function */
static int __init p9_virtio_init(void)
{
INIT_LIST_HEAD(&virtio_chan_list);
v9fs_register_trans(&p9_virtio_trans);
return register_virtio_driver(&p9_virtio_drv);
}
static void __exit p9_virtio_cleanup(void)
{
unregister_virtio_driver(&p9_virtio_drv);
v9fs_unregister_trans(&p9_virtio_trans);
}
module_init(p9_virtio_init);
module_exit(p9_virtio_cleanup);
MODULE_DEVICE_TABLE(virtio, id_table);
MODULE_AUTHOR("Eric Van Hensbergen <ericvh@gmail.com>");
MODULE_DESCRIPTION("Virtio 9p Transport");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,141 @@
/*
* net/9p/util.c
*
* This file contains some helper functions
*
* Copyright (C) 2007 by Latchesar Ionkov <lucho@ionkov.net>
* Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
* Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/parser.h>
#include <linux/idr.h>
#include <linux/slab.h>
#include <net/9p/9p.h>
/**
* struct p9_idpool - per-connection accounting for tag idpool
* @lock: protects the pool
* @pool: idr to allocate tag id from
*
*/
struct p9_idpool {
spinlock_t lock;
struct idr pool;
};
/**
* p9_idpool_create - create a new per-connection id pool
*
*/
struct p9_idpool *p9_idpool_create(void)
{
struct p9_idpool *p;
p = kmalloc(sizeof(struct p9_idpool), GFP_KERNEL);
if (!p)
return ERR_PTR(-ENOMEM);
spin_lock_init(&p->lock);
idr_init(&p->pool);
return p;
}
EXPORT_SYMBOL(p9_idpool_create);
/**
* p9_idpool_destroy - create a new per-connection id pool
* @p: idpool to destroy
*/
void p9_idpool_destroy(struct p9_idpool *p)
{
idr_destroy(&p->pool);
kfree(p);
}
EXPORT_SYMBOL(p9_idpool_destroy);
/**
* p9_idpool_get - allocate numeric id from pool
* @p: pool to allocate from
*
* Bugs: This seems to be an awful generic function, should it be in idr.c with
* the lock included in struct idr?
*/
int p9_idpool_get(struct p9_idpool *p)
{
int i;
unsigned long flags;
idr_preload(GFP_NOFS);
spin_lock_irqsave(&p->lock, flags);
/* no need to store exactly p, we just need something non-null */
i = idr_alloc(&p->pool, p, 0, 0, GFP_NOWAIT);
spin_unlock_irqrestore(&p->lock, flags);
idr_preload_end();
if (i < 0)
return -1;
p9_debug(P9_DEBUG_MUX, " id %d pool %p\n", i, p);
return i;
}
EXPORT_SYMBOL(p9_idpool_get);
/**
* p9_idpool_put - release numeric id from pool
* @id: numeric id which is being released
* @p: pool to release id into
*
* Bugs: This seems to be an awful generic function, should it be in idr.c with
* the lock included in struct idr?
*/
void p9_idpool_put(int id, struct p9_idpool *p)
{
unsigned long flags;
p9_debug(P9_DEBUG_MUX, " id %d pool %p\n", id, p);
spin_lock_irqsave(&p->lock, flags);
idr_remove(&p->pool, id);
spin_unlock_irqrestore(&p->lock, flags);
}
EXPORT_SYMBOL(p9_idpool_put);
/**
* p9_idpool_check - check if the specified id is available
* @id: id to check
* @p: pool to check
*/
int p9_idpool_check(int id, struct p9_idpool *p)
{
return idr_find(&p->pool, id) != NULL;
}
EXPORT_SYMBOL(p9_idpool_check);

View File

@@ -0,0 +1,700 @@
/*
* linux/fs/9p/v9fs.c
*
* This file contains functions assisting in mapping VFS to 9P2000
*
* Copyright (C) 2004-2008 by Eric Van Hensbergen <ericvh@gmail.com>
* Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/parser.h>
#include <linux/idr.h>
#include <linux/slab.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
#include <net/9p/transport.h>
#include "v9fs.h"
#include "v9fs_vfs.h"
#include "cache.h"
static DEFINE_SPINLOCK(v9fs_sessionlist_lock);
static LIST_HEAD(v9fs_sessionlist);
struct kmem_cache *v9fs_inode_cache;
/*
* Option Parsing (code inspired by NFS code)
* NOTE: each transport will parse its own options
*/
enum {
/* Options that take integer arguments */
Opt_debug, Opt_dfltuid, Opt_dfltgid, Opt_afid,
/* String options */
Opt_uname, Opt_remotename, Opt_cache, Opt_cachetag,
/* Options that take no arguments */
Opt_nodevmap,
/* Cache options */
Opt_cache_loose, Opt_fscache, Opt_mmap,
/* Access options */
Opt_access, Opt_posixacl,
/* Lock timeout option */
Opt_locktimeout,
/* Error token */
Opt_err
};
static const match_table_t tokens = {
{Opt_debug, "debug=%x"},
{Opt_dfltuid, "dfltuid=%u"},
{Opt_dfltgid, "dfltgid=%u"},
{Opt_afid, "afid=%u"},
{Opt_uname, "uname=%s"},
{Opt_remotename, "aname=%s"},
{Opt_nodevmap, "nodevmap"},
{Opt_cache, "cache=%s"},
{Opt_cache_loose, "loose"},
{Opt_fscache, "fscache"},
{Opt_mmap, "mmap"},
{Opt_cachetag, "cachetag=%s"},
{Opt_access, "access=%s"},
{Opt_posixacl, "posixacl"},
{Opt_locktimeout, "locktimeout=%u"},
{Opt_err, NULL}
};
/* Interpret mount options for cache mode */
static int get_cache_mode(char *s)
{
int version = -EINVAL;
if (!strcmp(s, "loose")) {
version = CACHE_LOOSE;
p9_debug(P9_DEBUG_9P, "Cache mode: loose\n");
} else if (!strcmp(s, "fscache")) {
version = CACHE_FSCACHE;
p9_debug(P9_DEBUG_9P, "Cache mode: fscache\n");
} else if (!strcmp(s, "mmap")) {
version = CACHE_MMAP;
p9_debug(P9_DEBUG_9P, "Cache mode: mmap\n");
} else if (!strcmp(s, "none")) {
version = CACHE_NONE;
p9_debug(P9_DEBUG_9P, "Cache mode: none\n");
} else
pr_info("Unknown Cache mode %s\n", s);
return version;
}
/**
* v9fs_parse_options - parse mount options into session structure
* @v9ses: existing v9fs session information
*
* Return 0 upon success, -ERRNO upon failure.
*/
static int v9fs_parse_options(struct v9fs_session_info *v9ses, char *opts)
{
char *options, *tmp_options;
substring_t args[MAX_OPT_ARGS];
char *p;
int option = 0;
char *s, *e;
int ret = 0;
/* setup defaults */
v9ses->afid = ~0;
v9ses->debug = 0;
v9ses->cache = CACHE_NONE;
#ifdef CONFIG_9P_FSCACHE
v9ses->cachetag = NULL;
#endif
v9ses->session_lock_timeout = P9_LOCK_TIMEOUT;
if (!opts)
return 0;
tmp_options = kstrdup(opts, GFP_KERNEL);
if (!tmp_options) {
ret = -ENOMEM;
goto fail_option_alloc;
}
options = tmp_options;
while ((p = strsep(&options, ",")) != NULL) {
int token, r;
if (!*p)
continue;
token = match_token(p, tokens, args);
switch (token) {
case Opt_debug:
r = match_int(&args[0], &option);
if (r < 0) {
p9_debug(P9_DEBUG_ERROR,
"integer field, but no integer?\n");
ret = r;
continue;
}
v9ses->debug = option;
#ifdef CONFIG_NET_9P_DEBUG
p9_debug_level = option;
#endif
break;
case Opt_dfltuid:
r = match_int(&args[0], &option);
if (r < 0) {
p9_debug(P9_DEBUG_ERROR,
"integer field, but no integer?\n");
ret = r;
continue;
}
v9ses->dfltuid = make_kuid(current_user_ns(), option);
if (!uid_valid(v9ses->dfltuid)) {
p9_debug(P9_DEBUG_ERROR,
"uid field, but not a uid?\n");
ret = -EINVAL;
continue;
}
break;
case Opt_dfltgid:
r = match_int(&args[0], &option);
if (r < 0) {
p9_debug(P9_DEBUG_ERROR,
"integer field, but no integer?\n");
ret = r;
continue;
}
v9ses->dfltgid = make_kgid(current_user_ns(), option);
if (!gid_valid(v9ses->dfltgid)) {
p9_debug(P9_DEBUG_ERROR,
"gid field, but not a gid?\n");
ret = -EINVAL;
continue;
}
break;
case Opt_afid:
r = match_int(&args[0], &option);
if (r < 0) {
p9_debug(P9_DEBUG_ERROR,
"integer field, but no integer?\n");
ret = r;
continue;
}
v9ses->afid = option;
break;
case Opt_uname:
kfree(v9ses->uname);
v9ses->uname = match_strdup(&args[0]);
if (!v9ses->uname) {
ret = -ENOMEM;
goto free_and_return;
}
break;
case Opt_remotename:
kfree(v9ses->aname);
v9ses->aname = match_strdup(&args[0]);
if (!v9ses->aname) {
ret = -ENOMEM;
goto free_and_return;
}
break;
case Opt_nodevmap:
v9ses->nodev = 1;
break;
case Opt_cache_loose:
v9ses->cache = CACHE_LOOSE;
break;
case Opt_fscache:
v9ses->cache = CACHE_FSCACHE;
break;
case Opt_mmap:
v9ses->cache = CACHE_MMAP;
break;
case Opt_cachetag:
#ifdef CONFIG_9P_FSCACHE
v9ses->cachetag = match_strdup(&args[0]);
#endif
break;
case Opt_cache:
s = match_strdup(&args[0]);
if (!s) {
ret = -ENOMEM;
p9_debug(P9_DEBUG_ERROR,
"problem allocating copy of cache arg\n");
goto free_and_return;
}
ret = get_cache_mode(s);
if (ret == -EINVAL) {
kfree(s);
goto free_and_return;
}
v9ses->cache = ret;
kfree(s);
break;
case Opt_access:
s = match_strdup(&args[0]);
if (!s) {
ret = -ENOMEM;
p9_debug(P9_DEBUG_ERROR,
"problem allocating copy of access arg\n");
goto free_and_return;
}
v9ses->flags &= ~V9FS_ACCESS_MASK;
if (strcmp(s, "user") == 0)
v9ses->flags |= V9FS_ACCESS_USER;
else if (strcmp(s, "any") == 0)
v9ses->flags |= V9FS_ACCESS_ANY;
else if (strcmp(s, "client") == 0) {
v9ses->flags |= V9FS_ACCESS_CLIENT;
} else {
uid_t uid;
v9ses->flags |= V9FS_ACCESS_SINGLE;
uid = simple_strtoul(s, &e, 10);
if (*e != '\0') {
ret = -EINVAL;
pr_info("Unknown access argument %s\n",
s);
kfree(s);
goto free_and_return;
}
v9ses->uid = make_kuid(current_user_ns(), uid);
if (!uid_valid(v9ses->uid)) {
ret = -EINVAL;
pr_info("Uknown uid %s\n", s);
kfree(s);
goto free_and_return;
}
}
kfree(s);
break;
case Opt_posixacl:
#ifdef CONFIG_9P_FS_POSIX_ACL
v9ses->flags |= V9FS_POSIX_ACL;
#else
p9_debug(P9_DEBUG_ERROR,
"Not defined CONFIG_9P_FS_POSIX_ACL. Ignoring posixacl option\n");
#endif
break;
case Opt_locktimeout:
r = match_int(&args[0], &option);
if (r < 0) {
p9_debug(P9_DEBUG_ERROR,
"integer field, but no integer?\n");
ret = r;
continue;
}
if (option < 1) {
p9_debug(P9_DEBUG_ERROR,
"locktimeout must be a greater than zero integer.\n");
ret = -EINVAL;
continue;
}
v9ses->session_lock_timeout = (long)option * HZ;
break;
default:
continue;
}
}
free_and_return:
kfree(tmp_options);
fail_option_alloc:
return ret;
}
/**
* v9fs_session_init - initialize session
* @v9ses: session information structure
* @dev_name: device being mounted
* @data: options
*
*/
struct p9_fid *v9fs_session_init(struct v9fs_session_info *v9ses,
const char *dev_name, char *data)
{
struct p9_fid *fid;
int rc = -ENOMEM;
v9ses->uname = kstrdup(V9FS_DEFUSER, GFP_KERNEL);
if (!v9ses->uname)
goto err_names;
v9ses->aname = kstrdup(V9FS_DEFANAME, GFP_KERNEL);
if (!v9ses->aname)
goto err_names;
init_rwsem(&v9ses->rename_sem);
rc = bdi_setup_and_register(&v9ses->bdi, "9p");
if (rc)
goto err_names;
v9ses->uid = INVALID_UID;
v9ses->dfltuid = V9FS_DEFUID;
v9ses->dfltgid = V9FS_DEFGID;
v9ses->clnt = p9_client_create(dev_name, data);
if (IS_ERR(v9ses->clnt)) {
rc = PTR_ERR(v9ses->clnt);
p9_debug(P9_DEBUG_ERROR, "problem initializing 9p client\n");
goto err_bdi;
}
v9ses->flags = V9FS_ACCESS_USER;
if (p9_is_proto_dotl(v9ses->clnt)) {
v9ses->flags = V9FS_ACCESS_CLIENT;
v9ses->flags |= V9FS_PROTO_2000L;
} else if (p9_is_proto_dotu(v9ses->clnt)) {
v9ses->flags |= V9FS_PROTO_2000U;
}
rc = v9fs_parse_options(v9ses, data);
if (rc < 0)
goto err_clnt;
v9ses->maxdata = v9ses->clnt->msize - P9_IOHDRSZ;
if (!v9fs_proto_dotl(v9ses) &&
((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_CLIENT)) {
/*
* We support ACCESS_CLIENT only for dotl.
* Fall back to ACCESS_USER
*/
v9ses->flags &= ~V9FS_ACCESS_MASK;
v9ses->flags |= V9FS_ACCESS_USER;
}
/*FIXME !! */
/* for legacy mode, fall back to V9FS_ACCESS_ANY */
if (!(v9fs_proto_dotu(v9ses) || v9fs_proto_dotl(v9ses)) &&
((v9ses->flags&V9FS_ACCESS_MASK) == V9FS_ACCESS_USER)) {
v9ses->flags &= ~V9FS_ACCESS_MASK;
v9ses->flags |= V9FS_ACCESS_ANY;
v9ses->uid = INVALID_UID;
}
if (!v9fs_proto_dotl(v9ses) ||
!((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_CLIENT)) {
/*
* We support ACL checks on clinet only if the protocol is
* 9P2000.L and access is V9FS_ACCESS_CLIENT.
*/
v9ses->flags &= ~V9FS_ACL_MASK;
}
fid = p9_client_attach(v9ses->clnt, NULL, v9ses->uname, INVALID_UID,
v9ses->aname);
if (IS_ERR(fid)) {
rc = PTR_ERR(fid);
p9_debug(P9_DEBUG_ERROR, "cannot attach\n");
goto err_clnt;
}
if ((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_SINGLE)
fid->uid = v9ses->uid;
else
fid->uid = INVALID_UID;
#ifdef CONFIG_9P_FSCACHE
/* register the session for caching */
v9fs_cache_session_get_cookie(v9ses);
#endif
spin_lock(&v9fs_sessionlist_lock);
list_add(&v9ses->slist, &v9fs_sessionlist);
spin_unlock(&v9fs_sessionlist_lock);
return fid;
err_clnt:
p9_client_destroy(v9ses->clnt);
err_bdi:
bdi_destroy(&v9ses->bdi);
err_names:
kfree(v9ses->uname);
kfree(v9ses->aname);
return ERR_PTR(rc);
}
/**
* v9fs_session_close - shutdown a session
* @v9ses: session information structure
*
*/
void v9fs_session_close(struct v9fs_session_info *v9ses)
{
if (v9ses->clnt) {
p9_client_destroy(v9ses->clnt);
v9ses->clnt = NULL;
}
#ifdef CONFIG_9P_FSCACHE
if (v9ses->fscache) {
v9fs_cache_session_put_cookie(v9ses);
kfree(v9ses->cachetag);
}
#endif
kfree(v9ses->uname);
kfree(v9ses->aname);
bdi_destroy(&v9ses->bdi);
spin_lock(&v9fs_sessionlist_lock);
list_del(&v9ses->slist);
spin_unlock(&v9fs_sessionlist_lock);
}
/**
* v9fs_session_cancel - terminate a session
* @v9ses: session to terminate
*
* mark transport as disconnected and cancel all pending requests.
*/
void v9fs_session_cancel(struct v9fs_session_info *v9ses) {
p9_debug(P9_DEBUG_ERROR, "cancel session %p\n", v9ses);
p9_client_disconnect(v9ses->clnt);
}
/**
* v9fs_session_begin_cancel - Begin terminate of a session
* @v9ses: session to terminate
*
* After this call we don't allow any request other than clunk.
*/
void v9fs_session_begin_cancel(struct v9fs_session_info *v9ses)
{
p9_debug(P9_DEBUG_ERROR, "begin cancel session %p\n", v9ses);
p9_client_begin_disconnect(v9ses->clnt);
}
extern int v9fs_error_init(void);
static struct kobject *v9fs_kobj;
#ifdef CONFIG_9P_FSCACHE
/**
* caches_show - list caches associated with a session
*
* Returns the size of buffer written.
*/
static ssize_t caches_show(struct kobject *kobj,
struct kobj_attribute *attr,
char *buf)
{
ssize_t n = 0, count = 0, limit = PAGE_SIZE;
struct v9fs_session_info *v9ses;
spin_lock(&v9fs_sessionlist_lock);
list_for_each_entry(v9ses, &v9fs_sessionlist, slist) {
if (v9ses->cachetag) {
n = snprintf(buf, limit, "%s\n", v9ses->cachetag);
if (n < 0) {
count = n;
break;
}
count += n;
limit -= n;
}
}
spin_unlock(&v9fs_sessionlist_lock);
return count;
}
static struct kobj_attribute v9fs_attr_cache = __ATTR_RO(caches);
#endif /* CONFIG_9P_FSCACHE */
static struct attribute *v9fs_attrs[] = {
#ifdef CONFIG_9P_FSCACHE
&v9fs_attr_cache.attr,
#endif
NULL,
};
static struct attribute_group v9fs_attr_group = {
.attrs = v9fs_attrs,
};
/**
* v9fs_sysfs_init - Initialize the v9fs sysfs interface
*
*/
static int __init v9fs_sysfs_init(void)
{
v9fs_kobj = kobject_create_and_add("9p", fs_kobj);
if (!v9fs_kobj)
return -ENOMEM;
if (sysfs_create_group(v9fs_kobj, &v9fs_attr_group)) {
kobject_put(v9fs_kobj);
return -ENOMEM;
}
return 0;
}
/**
* v9fs_sysfs_cleanup - Unregister the v9fs sysfs interface
*
*/
static void v9fs_sysfs_cleanup(void)
{
sysfs_remove_group(v9fs_kobj, &v9fs_attr_group);
kobject_put(v9fs_kobj);
}
static void v9fs_inode_init_once(void *foo)
{
struct v9fs_inode *v9inode = (struct v9fs_inode *)foo;
#ifdef CONFIG_9P_FSCACHE
v9inode->fscache = NULL;
#endif
memset(&v9inode->qid, 0, sizeof(v9inode->qid));
inode_init_once(&v9inode->vfs_inode);
}
/**
* v9fs_init_inode_cache - initialize a cache for 9P
* Returns 0 on success.
*/
static int v9fs_init_inode_cache(void)
{
v9fs_inode_cache = kmem_cache_create("v9fs_inode_cache",
sizeof(struct v9fs_inode),
0, (SLAB_RECLAIM_ACCOUNT|
SLAB_MEM_SPREAD),
v9fs_inode_init_once);
if (!v9fs_inode_cache)
return -ENOMEM;
return 0;
}
/**
* v9fs_destroy_inode_cache - destroy the cache of 9P inode
*
*/
static void v9fs_destroy_inode_cache(void)
{
/*
* Make sure all delayed rcu free inodes are flushed before we
* destroy cache.
*/
rcu_barrier();
kmem_cache_destroy(v9fs_inode_cache);
}
static int v9fs_cache_register(void)
{
int ret;
ret = v9fs_init_inode_cache();
if (ret < 0)
return ret;
#ifdef CONFIG_9P_FSCACHE
ret = fscache_register_netfs(&v9fs_cache_netfs);
if (ret < 0)
v9fs_destroy_inode_cache();
#endif
return ret;
}
static void v9fs_cache_unregister(void)
{
v9fs_destroy_inode_cache();
#ifdef CONFIG_9P_FSCACHE
fscache_unregister_netfs(&v9fs_cache_netfs);
#endif
}
/**
* init_v9fs - Initialize module
*
*/
static int __init init_v9fs(void)
{
int err;
pr_info("Installing v9fs 9p2000 file system support\n");
/* TODO: Setup list of registered trasnport modules */
err = v9fs_cache_register();
if (err < 0) {
pr_err("Failed to register v9fs for caching\n");
return err;
}
err = v9fs_sysfs_init();
if (err < 0) {
pr_err("Failed to register with sysfs\n");
goto out_cache;
}
err = register_filesystem(&v9fs_fs_type);
if (err < 0) {
pr_err("Failed to register filesystem\n");
goto out_sysfs_cleanup;
}
return 0;
out_sysfs_cleanup:
v9fs_sysfs_cleanup();
out_cache:
v9fs_cache_unregister();
return err;
}
/**
* exit_v9fs - shutdown module
*
*/
static void __exit exit_v9fs(void)
{
v9fs_sysfs_cleanup();
v9fs_cache_unregister();
unregister_filesystem(&v9fs_fs_type);
}
module_init(init_v9fs)
module_exit(exit_v9fs)
MODULE_AUTHOR("Latchesar Ionkov <lucho@ionkov.net>");
MODULE_AUTHOR("Eric Van Hensbergen <ericvh@gmail.com>");
MODULE_AUTHOR("Ron Minnich <rminnich@lanl.gov>");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,226 @@
/*
* V9FS definitions.
*
* Copyright (C) 2004-2008 by Eric Van Hensbergen <ericvh@gmail.com>
* Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#ifndef FS_9P_V9FS_H
#define FS_9P_V9FS_H
#include <linux/backing-dev.h>
/**
* enum p9_session_flags - option flags for each 9P session
* @V9FS_PROTO_2000U: whether or not to use 9P2000.u extensions
* @V9FS_PROTO_2000L: whether or not to use 9P2000.l extensions
* @V9FS_ACCESS_SINGLE: only the mounting user can access the hierarchy
* @V9FS_ACCESS_USER: a new attach will be issued for every user (default)
* @V9FS_ACCESS_CLIENT: Just like user, but access check is performed on client.
* @V9FS_ACCESS_ANY: use a single attach for all users
* @V9FS_ACCESS_MASK: bit mask of different ACCESS options
* @V9FS_POSIX_ACL: POSIX ACLs are enforced
*
* Session flags reflect options selected by users at mount time
*/
#define V9FS_ACCESS_ANY (V9FS_ACCESS_SINGLE | \
V9FS_ACCESS_USER | \
V9FS_ACCESS_CLIENT)
#define V9FS_ACCESS_MASK V9FS_ACCESS_ANY
#define V9FS_ACL_MASK V9FS_POSIX_ACL
enum p9_session_flags {
V9FS_PROTO_2000U = 0x01,
V9FS_PROTO_2000L = 0x02,
V9FS_ACCESS_SINGLE = 0x04,
V9FS_ACCESS_USER = 0x08,
V9FS_ACCESS_CLIENT = 0x10,
V9FS_POSIX_ACL = 0x20
};
/* possible values of ->cache */
/**
* enum p9_cache_modes - user specified cache preferences
* @CACHE_NONE: do not cache data, dentries, or directory contents (default)
* @CACHE_LOOSE: cache data, dentries, and directory contents w/no consistency
*
* eventually support loose, tight, time, session, default always none
*/
enum p9_cache_modes {
CACHE_NONE,
CACHE_MMAP,
CACHE_LOOSE,
CACHE_FSCACHE,
};
/**
* struct v9fs_session_info - per-instance session information
* @flags: session options of type &p9_session_flags
* @nodev: set to 1 to disable device mapping
* @debug: debug level
* @afid: authentication handle
* @cache: cache mode of type &p9_cache_modes
* @cachetag: the tag of the cache associated with this session
* @fscache: session cookie associated with FS-Cache
* @uname: string user name to mount hierarchy as
* @aname: mount specifier for remote hierarchy
* @maxdata: maximum data to be sent/recvd per protocol message
* @dfltuid: default numeric userid to mount hierarchy as
* @dfltgid: default numeric groupid to mount hierarchy as
* @uid: if %V9FS_ACCESS_SINGLE, the numeric uid which mounted the hierarchy
* @clnt: reference to 9P network client instantiated for this session
* @slist: reference to list of registered 9p sessions
*
* This structure holds state for each session instance established during
* a sys_mount() .
*
* Bugs: there seems to be a lot of state which could be condensed and/or
* removed.
*/
struct v9fs_session_info {
/* options */
unsigned char flags;
unsigned char nodev;
unsigned short debug;
unsigned int afid;
unsigned int cache;
#ifdef CONFIG_9P_FSCACHE
char *cachetag;
struct fscache_cookie *fscache;
#endif
char *uname; /* user name to mount as */
char *aname; /* name of remote hierarchy being mounted */
unsigned int maxdata; /* max data for client interface */
kuid_t dfltuid; /* default uid/muid for legacy support */
kgid_t dfltgid; /* default gid for legacy support */
kuid_t uid; /* if ACCESS_SINGLE, the uid that has access */
struct p9_client *clnt; /* 9p client */
struct list_head slist; /* list of sessions registered with v9fs */
struct backing_dev_info bdi;
struct rw_semaphore rename_sem;
long session_lock_timeout; /* retry interval for blocking locks */
};
/* cache_validity flags */
#define V9FS_INO_INVALID_ATTR 0x01
struct v9fs_inode {
#ifdef CONFIG_9P_FSCACHE
struct mutex fscache_lock;
struct fscache_cookie *fscache;
#endif
struct p9_qid qid;
unsigned int cache_validity;
struct p9_fid *writeback_fid;
struct mutex v_mutex;
struct inode vfs_inode;
};
static inline struct v9fs_inode *V9FS_I(const struct inode *inode)
{
return container_of(inode, struct v9fs_inode, vfs_inode);
}
struct p9_fid *v9fs_session_init(struct v9fs_session_info *, const char *,
char *);
extern void v9fs_session_close(struct v9fs_session_info *v9ses);
extern void v9fs_session_cancel(struct v9fs_session_info *v9ses);
extern void v9fs_session_begin_cancel(struct v9fs_session_info *v9ses);
extern struct dentry *v9fs_vfs_lookup(struct inode *dir, struct dentry *dentry,
unsigned int flags);
extern int v9fs_vfs_unlink(struct inode *i, struct dentry *d);
extern int v9fs_vfs_rmdir(struct inode *i, struct dentry *d);
extern int v9fs_vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
struct inode *new_dir, struct dentry *new_dentry);
extern struct inode *v9fs_inode_from_fid(struct v9fs_session_info *v9ses,
struct p9_fid *fid,
struct super_block *sb, int new);
extern const struct inode_operations v9fs_dir_inode_operations_dotl;
extern const struct inode_operations v9fs_file_inode_operations_dotl;
extern const struct inode_operations v9fs_symlink_inode_operations_dotl;
extern struct inode *v9fs_inode_from_fid_dotl(struct v9fs_session_info *v9ses,
struct p9_fid *fid,
struct super_block *sb, int new);
/* other default globals */
#define V9FS_PORT 564
#define V9FS_DEFUSER "nobody"
#define V9FS_DEFANAME ""
#define V9FS_DEFUID KUIDT_INIT(-2)
#define V9FS_DEFGID KGIDT_INIT(-2)
static inline struct v9fs_session_info *v9fs_inode2v9ses(struct inode *inode)
{
return (inode->i_sb->s_fs_info);
}
static inline struct v9fs_session_info *v9fs_dentry2v9ses(struct dentry *dentry)
{
return dentry->d_sb->s_fs_info;
}
static inline int v9fs_proto_dotu(struct v9fs_session_info *v9ses)
{
return v9ses->flags & V9FS_PROTO_2000U;
}
static inline int v9fs_proto_dotl(struct v9fs_session_info *v9ses)
{
return v9ses->flags & V9FS_PROTO_2000L;
}
/**
* v9fs_get_inode_from_fid - Helper routine to populate an inode by
* issuing a attribute request
* @v9ses: session information
* @fid: fid to issue attribute request for
* @sb: superblock on which to create inode
*
*/
static inline struct inode *
v9fs_get_inode_from_fid(struct v9fs_session_info *v9ses, struct p9_fid *fid,
struct super_block *sb)
{
if (v9fs_proto_dotl(v9ses))
return v9fs_inode_from_fid_dotl(v9ses, fid, sb, 0);
else
return v9fs_inode_from_fid(v9ses, fid, sb, 0);
}
/**
* v9fs_get_new_inode_from_fid - Helper routine to populate an inode by
* issuing a attribute request
* @v9ses: session information
* @fid: fid to issue attribute request for
* @sb: superblock on which to create inode
*
*/
static inline struct inode *
v9fs_get_new_inode_from_fid(struct v9fs_session_info *v9ses, struct p9_fid *fid,
struct super_block *sb)
{
if (v9fs_proto_dotl(v9ses))
return v9fs_inode_from_fid_dotl(v9ses, fid, sb, 1);
else
return v9fs_inode_from_fid(v9ses, fid, sb, 1);
}
#endif

View File

@@ -0,0 +1,105 @@
/*
* V9FS VFS extensions.
*
* Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
* Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#ifndef FS_9P_V9FS_VFS_H
#define FS_9P_V9FS_VFS_H
/* plan9 semantics are that created files are implicitly opened.
* But linux semantics are that you call create, then open.
* the plan9 approach is superior as it provides an atomic
* open.
* we track the create fid here. When the file is opened, if fidopen is
* non-zero, we use the fid and can skip some steps.
* there may be a better way to do this, but I don't know it.
* one BAD way is to clunk the fid on create, then open it again:
* you lose the atomicity of file open
*/
/* special case:
* unlink calls remove, which is an implicit clunk. So we have to track
* that kind of thing so that we don't try to clunk a dead fid.
*/
#define P9_LOCK_TIMEOUT (30*HZ)
/* flags for v9fs_stat2inode() & v9fs_stat2inode_dotl() */
#define V9FS_STAT2INODE_KEEP_ISIZE 1
extern struct file_system_type v9fs_fs_type;
extern const struct address_space_operations v9fs_addr_operations;
extern const struct file_operations v9fs_file_operations;
extern const struct file_operations v9fs_file_operations_dotl;
extern const struct file_operations v9fs_dir_operations;
extern const struct file_operations v9fs_dir_operations_dotl;
extern const struct dentry_operations v9fs_dentry_operations;
extern const struct dentry_operations v9fs_cached_dentry_operations;
extern const struct file_operations v9fs_cached_file_operations;
extern const struct file_operations v9fs_cached_file_operations_dotl;
extern const struct file_operations v9fs_mmap_file_operations;
extern const struct file_operations v9fs_mmap_file_operations_dotl;
extern struct kmem_cache *v9fs_inode_cache;
struct inode *v9fs_alloc_inode(struct super_block *sb);
void v9fs_destroy_inode(struct inode *inode);
struct inode *v9fs_get_inode(struct super_block *sb, umode_t mode, dev_t);
int v9fs_init_inode(struct v9fs_session_info *v9ses,
struct inode *inode, umode_t mode, dev_t);
void v9fs_evict_inode(struct inode *inode);
ino_t v9fs_qid2ino(struct p9_qid *qid);
void v9fs_stat2inode(struct p9_wstat *stat, struct inode *inode,
struct super_block *sb, unsigned int flags);
void v9fs_stat2inode_dotl(struct p9_stat_dotl *stat, struct inode *inode,
unsigned int flags);
int v9fs_dir_release(struct inode *inode, struct file *filp);
int v9fs_file_open(struct inode *inode, struct file *file);
void v9fs_inode2stat(struct inode *inode, struct p9_wstat *stat);
int v9fs_uflags2omode(int uflags, int extended);
void v9fs_blank_wstat(struct p9_wstat *wstat);
int v9fs_vfs_setattr_dotl(struct dentry *, struct iattr *);
int v9fs_file_fsync_dotl(struct file *filp, loff_t start, loff_t end,
int datasync);
int v9fs_refresh_inode(struct p9_fid *fid, struct inode *inode);
int v9fs_refresh_inode_dotl(struct p9_fid *fid, struct inode *inode);
static inline void v9fs_invalidate_inode_attr(struct inode *inode)
{
struct v9fs_inode *v9inode;
v9inode = V9FS_I(inode);
v9inode->cache_validity |= V9FS_INO_INVALID_ATTR;
return;
}
int v9fs_open_to_dotl_flags(int flags);
static inline void v9fs_i_size_write(struct inode *inode, loff_t i_size)
{
/*
* 32-bit need the lock, concurrent updates could break the
* sequences and make i_size_read() loop forever.
* 64-bit updates are atomic and can skip the locking.
*/
if (sizeof(i_size) > sizeof(long))
spin_lock(&inode->i_lock);
i_size_write(inode, i_size);
if (sizeof(i_size) > sizeof(long))
spin_unlock(&inode->i_lock);
}
#endif

View File

@@ -0,0 +1,351 @@
/*
* linux/fs/9p/vfs_addr.c
*
* This file contians vfs address (mmap) ops for 9P2000.
*
* Copyright (C) 2005 by Eric Van Hensbergen <ericvh@gmail.com>
* Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/stat.h>
#include <linux/string.h>
#include <linux/inet.h>
#include <linux/pagemap.h>
#include <linux/idr.h>
#include <linux/sched.h>
#include <linux/uio.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
#include "v9fs.h"
#include "v9fs_vfs.h"
#include "cache.h"
#include "fid.h"
/**
* v9fs_fid_readpage - read an entire page in from 9P
*
* @fid: fid being read
* @page: structure to page
*
*/
static int v9fs_fid_readpage(struct p9_fid *fid, struct page *page)
{
struct inode *inode = page->mapping->host;
struct bio_vec bvec = {.bv_page = page, .bv_len = PAGE_SIZE};
struct iov_iter to;
int retval, err;
p9_debug(P9_DEBUG_VFS, "\n");
BUG_ON(!PageLocked(page));
retval = v9fs_readpage_from_fscache(inode, page);
if (retval == 0)
return retval;
iov_iter_bvec(&to, ITER_BVEC | READ, &bvec, 1, PAGE_SIZE);
retval = p9_client_read(fid, page_offset(page), &to, &err);
if (err) {
v9fs_uncache_page(inode, page);
retval = err;
goto done;
}
zero_user(page, retval, PAGE_SIZE - retval);
flush_dcache_page(page);
SetPageUptodate(page);
v9fs_readpage_to_fscache(inode, page);
retval = 0;
done:
unlock_page(page);
return retval;
}
/**
* v9fs_vfs_readpage - read an entire page in from 9P
*
* @filp: file being read
* @page: structure to page
*
*/
static int v9fs_vfs_readpage(struct file *filp, struct page *page)
{
return v9fs_fid_readpage(filp->private_data, page);
}
/**
* v9fs_vfs_readpages - read a set of pages from 9P
*
* @filp: file being read
* @mapping: the address space
* @pages: list of pages to read
* @nr_pages: count of pages to read
*
*/
static int v9fs_vfs_readpages(struct file *filp, struct address_space *mapping,
struct list_head *pages, unsigned nr_pages)
{
int ret = 0;
struct inode *inode;
inode = mapping->host;
p9_debug(P9_DEBUG_VFS, "inode: %p file: %p\n", inode, filp);
ret = v9fs_readpages_from_fscache(inode, mapping, pages, &nr_pages);
if (ret == 0)
return ret;
ret = read_cache_pages(mapping, pages, (void *)v9fs_vfs_readpage, filp);
p9_debug(P9_DEBUG_VFS, " = %d\n", ret);
return ret;
}
/**
* v9fs_release_page - release the private state associated with a page
*
* Returns 1 if the page can be released, false otherwise.
*/
static int v9fs_release_page(struct page *page, gfp_t gfp)
{
if (PagePrivate(page))
return 0;
return v9fs_fscache_release_page(page, gfp);
}
/**
* v9fs_invalidate_page - Invalidate a page completely or partially
*
* @page: structure to page
* @offset: offset in the page
*/
static void v9fs_invalidate_page(struct page *page, unsigned int offset,
unsigned int length)
{
/*
* If called with zero offset, we should release
* the private state assocated with the page
*/
if (offset == 0 && length == PAGE_CACHE_SIZE)
v9fs_fscache_invalidate_page(page);
}
static int v9fs_vfs_writepage_locked(struct page *page)
{
struct inode *inode = page->mapping->host;
struct v9fs_inode *v9inode = V9FS_I(inode);
loff_t size = i_size_read(inode);
struct iov_iter from;
struct bio_vec bvec;
int err, len;
if (page->index == size >> PAGE_CACHE_SHIFT)
len = size & ~PAGE_CACHE_MASK;
else
len = PAGE_CACHE_SIZE;
bvec.bv_page = page;
bvec.bv_offset = 0;
bvec.bv_len = len;
iov_iter_bvec(&from, ITER_BVEC | WRITE, &bvec, 1, len);
/* We should have writeback_fid always set */
BUG_ON(!v9inode->writeback_fid);
set_page_writeback(page);
p9_client_write(v9inode->writeback_fid, page_offset(page), &from, &err);
end_page_writeback(page);
return err;
}
static int v9fs_vfs_writepage(struct page *page, struct writeback_control *wbc)
{
int retval;
p9_debug(P9_DEBUG_VFS, "page %p\n", page);
retval = v9fs_vfs_writepage_locked(page);
if (retval < 0) {
if (retval == -EAGAIN) {
redirty_page_for_writepage(wbc, page);
retval = 0;
} else {
SetPageError(page);
mapping_set_error(page->mapping, retval);
}
} else
retval = 0;
unlock_page(page);
return retval;
}
/**
* v9fs_launder_page - Writeback a dirty page
* Returns 0 on success.
*/
static int v9fs_launder_page(struct page *page)
{
int retval;
struct inode *inode = page->mapping->host;
v9fs_fscache_wait_on_page_write(inode, page);
if (clear_page_dirty_for_io(page)) {
retval = v9fs_vfs_writepage_locked(page);
if (retval)
return retval;
}
return 0;
}
/**
* v9fs_direct_IO - 9P address space operation for direct I/O
* @iocb: target I/O control block
* @pos: offset in file to begin the operation
*
* The presence of v9fs_direct_IO() in the address space ops vector
* allowes open() O_DIRECT flags which would have failed otherwise.
*
* In the non-cached mode, we shunt off direct read and write requests before
* the VFS gets them, so this method should never be called.
*
* Direct IO is not 'yet' supported in the cached mode. Hence when
* this routine is called through generic_file_aio_read(), the read/write fails
* with an error.
*
*/
static ssize_t
v9fs_direct_IO(struct kiocb *iocb, struct iov_iter *iter, loff_t pos)
{
struct file *file = iocb->ki_filp;
ssize_t n;
int err = 0;
if (iov_iter_rw(iter) == WRITE) {
n = p9_client_write(file->private_data, pos, iter, &err);
if (n) {
struct inode *inode = file_inode(file);
loff_t i_size = i_size_read(inode);
if (pos + n > i_size)
inode_add_bytes(inode, pos + n - i_size);
}
} else {
n = p9_client_read(file->private_data, pos, iter, &err);
}
return n ? n : err;
}
static int v9fs_write_begin(struct file *filp, struct address_space *mapping,
loff_t pos, unsigned len, unsigned flags,
struct page **pagep, void **fsdata)
{
int retval = 0;
struct page *page;
struct v9fs_inode *v9inode;
pgoff_t index = pos >> PAGE_CACHE_SHIFT;
struct inode *inode = mapping->host;
p9_debug(P9_DEBUG_VFS, "filp %p, mapping %p\n", filp, mapping);
v9inode = V9FS_I(inode);
start:
page = grab_cache_page_write_begin(mapping, index, flags);
if (!page) {
retval = -ENOMEM;
goto out;
}
BUG_ON(!v9inode->writeback_fid);
if (PageUptodate(page))
goto out;
if (len == PAGE_CACHE_SIZE)
goto out;
retval = v9fs_fid_readpage(v9inode->writeback_fid, page);
page_cache_release(page);
if (!retval)
goto start;
out:
*pagep = page;
return retval;
}
static int v9fs_write_end(struct file *filp, struct address_space *mapping,
loff_t pos, unsigned len, unsigned copied,
struct page *page, void *fsdata)
{
loff_t last_pos = pos + copied;
struct inode *inode = page->mapping->host;
p9_debug(P9_DEBUG_VFS, "filp %p, mapping %p\n", filp, mapping);
if (unlikely(copied < len)) {
/*
* zero out the rest of the area
*/
unsigned from = pos & (PAGE_CACHE_SIZE - 1);
zero_user(page, from + copied, len - copied);
flush_dcache_page(page);
}
if (!PageUptodate(page))
SetPageUptodate(page);
/*
* No need to use i_size_read() here, the i_size
* cannot change under us because we hold the i_mutex.
*/
if (last_pos > inode->i_size) {
inode_add_bytes(inode, last_pos - inode->i_size);
i_size_write(inode, last_pos);
}
set_page_dirty(page);
unlock_page(page);
page_cache_release(page);
return copied;
}
const struct address_space_operations v9fs_addr_operations = {
.readpage = v9fs_vfs_readpage,
.readpages = v9fs_vfs_readpages,
.set_page_dirty = __set_page_dirty_nobuffers,
.writepage = v9fs_vfs_writepage,
.write_begin = v9fs_write_begin,
.write_end = v9fs_write_end,
.releasepage = v9fs_release_page,
.invalidatepage = v9fs_invalidate_page,
.launder_page = v9fs_launder_page,
.direct_IO = v9fs_direct_IO,
};

View File

@@ -0,0 +1,122 @@
/*
* linux/fs/9p/vfs_dentry.c
*
* This file contians vfs dentry ops for the 9P2000 protocol.
*
* Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
* Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/pagemap.h>
#include <linux/stat.h>
#include <linux/string.h>
#include <linux/inet.h>
#include <linux/namei.h>
#include <linux/idr.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
#include "v9fs.h"
#include "v9fs_vfs.h"
#include "fid.h"
/**
* v9fs_cached_dentry_delete - called when dentry refcount equals 0
* @dentry: dentry in question
*
*/
static int v9fs_cached_dentry_delete(const struct dentry *dentry)
{
p9_debug(P9_DEBUG_VFS, " dentry: %pd (%p)\n",
dentry, dentry);
/* Don't cache negative dentries */
if (d_really_is_negative(dentry))
return 1;
return 0;
}
/**
* v9fs_dentry_release - called when dentry is going to be freed
* @dentry: dentry that is being release
*
*/
static void v9fs_dentry_release(struct dentry *dentry)
{
struct hlist_node *p, *n;
p9_debug(P9_DEBUG_VFS, " dentry: %pd (%p)\n",
dentry, dentry);
hlist_for_each_safe(p, n, (struct hlist_head *)&dentry->d_fsdata)
p9_client_clunk(hlist_entry(p, struct p9_fid, dlist));
dentry->d_fsdata = NULL;
}
static int v9fs_lookup_revalidate(struct dentry *dentry, unsigned int flags)
{
struct p9_fid *fid;
struct inode *inode;
struct v9fs_inode *v9inode;
if (flags & LOOKUP_RCU)
return -ECHILD;
inode = d_inode(dentry);
if (!inode)
goto out_valid;
v9inode = V9FS_I(inode);
if (v9inode->cache_validity & V9FS_INO_INVALID_ATTR) {
int retval;
struct v9fs_session_info *v9ses;
fid = v9fs_fid_lookup(dentry);
if (IS_ERR(fid))
return PTR_ERR(fid);
v9ses = v9fs_inode2v9ses(inode);
if (v9fs_proto_dotl(v9ses))
retval = v9fs_refresh_inode_dotl(fid, inode);
else
retval = v9fs_refresh_inode(fid, inode);
if (retval == -ENOENT)
return 0;
if (retval < 0)
return retval;
}
out_valid:
return 1;
}
const struct dentry_operations v9fs_cached_dentry_operations = {
.d_revalidate = v9fs_lookup_revalidate,
.d_weak_revalidate = v9fs_lookup_revalidate,
.d_delete = v9fs_cached_dentry_delete,
.d_release = v9fs_dentry_release,
};
const struct dentry_operations v9fs_dentry_operations = {
.d_delete = always_delete_dentry,
.d_release = v9fs_dentry_release,
};

View File

@@ -0,0 +1,248 @@
/*
* linux/fs/9p/vfs_dir.c
*
* This file contains vfs directory ops for the 9P2000 protocol.
*
* Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
* Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/stat.h>
#include <linux/string.h>
#include <linux/sched.h>
#include <linux/inet.h>
#include <linux/idr.h>
#include <linux/slab.h>
#include <linux/uio.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
#include "v9fs.h"
#include "v9fs_vfs.h"
#include "fid.h"
/**
* struct p9_rdir - readdir accounting
* @head: start offset of current dirread buffer
* @tail: end offset of current dirread buffer
* @buf: dirread buffer
*
* private structure for keeping track of readdir
* allocated on demand
*/
struct p9_rdir {
int head;
int tail;
uint8_t buf[];
};
/**
* dt_type - return file type
* @mistat: mistat structure
*
*/
static inline int dt_type(struct p9_wstat *mistat)
{
unsigned long perm = mistat->mode;
int rettype = DT_REG;
if (perm & P9_DMDIR)
rettype = DT_DIR;
if (perm & P9_DMSYMLINK)
rettype = DT_LNK;
return rettype;
}
/**
* v9fs_alloc_rdir_buf - Allocate buffer used for read and readdir
* @filp: opened file structure
* @buflen: Length in bytes of buffer to allocate
*
*/
static struct p9_rdir *v9fs_alloc_rdir_buf(struct file *filp, int buflen)
{
struct p9_fid *fid = filp->private_data;
if (!fid->rdir)
fid->rdir = kzalloc(sizeof(struct p9_rdir) + buflen, GFP_KERNEL);
return fid->rdir;
}
/**
* v9fs_dir_readdir - iterate through a directory
* @file: opened file structure
* @ctx: actor we feed the entries to
*
*/
static int v9fs_dir_readdir(struct file *file, struct dir_context *ctx)
{
bool over;
struct p9_wstat st;
int err = 0;
struct p9_fid *fid;
int buflen;
struct p9_rdir *rdir;
struct kvec kvec;
p9_debug(P9_DEBUG_VFS, "name %pD\n", file);
fid = file->private_data;
buflen = fid->clnt->msize - P9_IOHDRSZ;
rdir = v9fs_alloc_rdir_buf(file, buflen);
if (!rdir)
return -ENOMEM;
kvec.iov_base = rdir->buf;
kvec.iov_len = buflen;
while (1) {
if (rdir->tail == rdir->head) {
struct iov_iter to;
int n;
iov_iter_kvec(&to, READ | ITER_KVEC, &kvec, 1, buflen);
n = p9_client_read(file->private_data, ctx->pos, &to,
&err);
if (err)
return err;
if (n == 0)
return 0;
rdir->head = 0;
rdir->tail = n;
}
while (rdir->head < rdir->tail) {
err = p9stat_read(fid->clnt, rdir->buf + rdir->head,
rdir->tail - rdir->head, &st);
if (err <= 0) {
p9_debug(P9_DEBUG_VFS, "returned %d\n", err);
return -EIO;
}
over = !dir_emit(ctx, st.name, strlen(st.name),
v9fs_qid2ino(&st.qid), dt_type(&st));
p9stat_free(&st);
if (over)
return 0;
rdir->head += err;
ctx->pos += err;
}
}
}
/**
* v9fs_dir_readdir_dotl - iterate through a directory
* @file: opened file structure
* @ctx: actor we feed the entries to
*
*/
static int v9fs_dir_readdir_dotl(struct file *file, struct dir_context *ctx)
{
int err = 0;
struct p9_fid *fid;
int buflen;
struct p9_rdir *rdir;
struct p9_dirent curdirent;
p9_debug(P9_DEBUG_VFS, "name %pD\n", file);
fid = file->private_data;
buflen = fid->clnt->msize - P9_READDIRHDRSZ;
rdir = v9fs_alloc_rdir_buf(file, buflen);
if (!rdir)
return -ENOMEM;
while (1) {
if (rdir->tail == rdir->head) {
err = p9_client_readdir(fid, rdir->buf, buflen,
ctx->pos);
if (err <= 0)
return err;
rdir->head = 0;
rdir->tail = err;
}
while (rdir->head < rdir->tail) {
err = p9dirent_read(fid->clnt, rdir->buf + rdir->head,
rdir->tail - rdir->head,
&curdirent);
if (err < 0) {
p9_debug(P9_DEBUG_VFS, "returned %d\n", err);
return -EIO;
}
if (!dir_emit(ctx, curdirent.d_name,
strlen(curdirent.d_name),
v9fs_qid2ino(&curdirent.qid),
curdirent.d_type))
return 0;
ctx->pos = curdirent.d_off;
rdir->head += err;
}
}
}
/**
* v9fs_dir_release - close a directory
* @inode: inode of the directory
* @filp: file pointer to a directory
*
*/
int v9fs_dir_release(struct inode *inode, struct file *filp)
{
struct p9_fid *fid;
fid = filp->private_data;
p9_debug(P9_DEBUG_VFS, "inode: %p filp: %p fid: %d\n",
inode, filp, fid ? fid->fid : -1);
if (fid)
p9_client_clunk(fid);
return 0;
}
const struct file_operations v9fs_dir_operations = {
.read = generic_read_dir,
.llseek = generic_file_llseek,
.iterate = v9fs_dir_readdir,
.open = v9fs_file_open,
.release = v9fs_dir_release,
};
const struct file_operations v9fs_dir_operations_dotl = {
.read = generic_read_dir,
.llseek = generic_file_llseek,
.iterate = v9fs_dir_readdir_dotl,
.open = v9fs_file_open,
.release = v9fs_dir_release,
.fsync = v9fs_file_fsync_dotl,
};

View File

@@ -0,0 +1,726 @@
/*
* linux/fs/9p/vfs_file.c
*
* This file contians vfs file ops for 9P2000.
*
* Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
* Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/file.h>
#include <linux/stat.h>
#include <linux/string.h>
#include <linux/inet.h>
#include <linux/list.h>
#include <linux/pagemap.h>
#include <linux/utsname.h>
#include <asm/uaccess.h>
#include <linux/idr.h>
#include <linux/uio.h>
#include <linux/slab.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
#include "v9fs.h"
#include "v9fs_vfs.h"
#include "fid.h"
#include "cache.h"
static const struct vm_operations_struct v9fs_file_vm_ops;
static const struct vm_operations_struct v9fs_mmap_file_vm_ops;
/**
* v9fs_file_open - open a file (or directory)
* @inode: inode to be opened
* @file: file being opened
*
*/
int v9fs_file_open(struct inode *inode, struct file *file)
{
int err;
struct v9fs_inode *v9inode;
struct v9fs_session_info *v9ses;
struct p9_fid *fid;
int omode;
p9_debug(P9_DEBUG_VFS, "inode: %p file: %p\n", inode, file);
v9inode = V9FS_I(inode);
v9ses = v9fs_inode2v9ses(inode);
if (v9fs_proto_dotl(v9ses))
omode = v9fs_open_to_dotl_flags(file->f_flags);
else
omode = v9fs_uflags2omode(file->f_flags,
v9fs_proto_dotu(v9ses));
fid = file->private_data;
if (!fid) {
fid = v9fs_fid_clone(file_dentry(file));
if (IS_ERR(fid))
return PTR_ERR(fid);
err = p9_client_open(fid, omode);
if (err < 0) {
p9_client_clunk(fid);
return err;
}
if ((file->f_flags & O_APPEND) &&
(!v9fs_proto_dotu(v9ses) && !v9fs_proto_dotl(v9ses)))
generic_file_llseek(file, 0, SEEK_END);
}
file->private_data = fid;
mutex_lock(&v9inode->v_mutex);
if ((v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) &&
!v9inode->writeback_fid &&
((file->f_flags & O_ACCMODE) != O_RDONLY)) {
/*
* clone a fid and add it to writeback_fid
* we do it during open time instead of
* page dirty time via write_begin/page_mkwrite
* because we want write after unlink usecase
* to work.
*/
fid = v9fs_writeback_fid(file_dentry(file));
if (IS_ERR(fid)) {
err = PTR_ERR(fid);
mutex_unlock(&v9inode->v_mutex);
goto out_error;
}
v9inode->writeback_fid = (void *) fid;
}
mutex_unlock(&v9inode->v_mutex);
if (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE)
v9fs_cache_inode_set_cookie(inode, file);
return 0;
out_error:
p9_client_clunk(file->private_data);
file->private_data = NULL;
return err;
}
/**
* v9fs_file_lock - lock a file (or directory)
* @filp: file to be locked
* @cmd: lock command
* @fl: file lock structure
*
* Bugs: this looks like a local only lock, we should extend into 9P
* by using open exclusive
*/
static int v9fs_file_lock(struct file *filp, int cmd, struct file_lock *fl)
{
int res = 0;
struct inode *inode = file_inode(filp);
p9_debug(P9_DEBUG_VFS, "filp: %p lock: %p\n", filp, fl);
/* No mandatory locks */
if (__mandatory_lock(inode) && fl->fl_type != F_UNLCK)
return -ENOLCK;
if ((IS_SETLK(cmd) || IS_SETLKW(cmd)) && fl->fl_type != F_UNLCK) {
filemap_write_and_wait(inode->i_mapping);
invalidate_mapping_pages(&inode->i_data, 0, -1);
}
return res;
}
static int v9fs_file_do_lock(struct file *filp, int cmd, struct file_lock *fl)
{
struct p9_flock flock;
struct p9_fid *fid;
uint8_t status = P9_LOCK_ERROR;
int res = 0;
unsigned char fl_type;
struct v9fs_session_info *v9ses;
fid = filp->private_data;
BUG_ON(fid == NULL);
if ((fl->fl_flags & FL_POSIX) != FL_POSIX)
BUG();
res = locks_lock_file_wait(filp, fl);
if (res < 0)
goto out;
/* convert posix lock to p9 tlock args */
memset(&flock, 0, sizeof(flock));
/* map the lock type */
switch (fl->fl_type) {
case F_RDLCK:
flock.type = P9_LOCK_TYPE_RDLCK;
break;
case F_WRLCK:
flock.type = P9_LOCK_TYPE_WRLCK;
break;
case F_UNLCK:
flock.type = P9_LOCK_TYPE_UNLCK;
break;
}
flock.start = fl->fl_start;
if (fl->fl_end == OFFSET_MAX)
flock.length = 0;
else
flock.length = fl->fl_end - fl->fl_start + 1;
flock.proc_id = fl->fl_pid;
flock.client_id = fid->clnt->name;
if (IS_SETLKW(cmd))
flock.flags = P9_LOCK_FLAGS_BLOCK;
v9ses = v9fs_inode2v9ses(file_inode(filp));
/*
* if its a blocked request and we get P9_LOCK_BLOCKED as the status
* for lock request, keep on trying
*/
for (;;) {
res = p9_client_lock_dotl(fid, &flock, &status);
if (res < 0)
goto out_unlock;
if (status != P9_LOCK_BLOCKED)
break;
if (status == P9_LOCK_BLOCKED && !IS_SETLKW(cmd))
break;
if (schedule_timeout_interruptible(v9ses->session_lock_timeout)
!= 0)
break;
/*
* p9_client_lock_dotl overwrites flock.client_id with the
* server message, free and reuse the client name
*/
if (flock.client_id != fid->clnt->name) {
kfree(flock.client_id);
flock.client_id = fid->clnt->name;
}
}
/* map 9p status to VFS status */
switch (status) {
case P9_LOCK_SUCCESS:
res = 0;
break;
case P9_LOCK_BLOCKED:
res = -EAGAIN;
break;
default:
WARN_ONCE(1, "unknown lock status code: %d\n", status);
/* fallthough */
case P9_LOCK_ERROR:
case P9_LOCK_GRACE:
res = -ENOLCK;
break;
}
out_unlock:
/*
* incase server returned error for lock request, revert
* it locally
*/
if (res < 0 && fl->fl_type != F_UNLCK) {
fl_type = fl->fl_type;
fl->fl_type = F_UNLCK;
/* Even if this fails we want to return the remote error */
locks_lock_file_wait(filp, fl);
fl->fl_type = fl_type;
}
if (flock.client_id != fid->clnt->name)
kfree(flock.client_id);
out:
return res;
}
static int v9fs_file_getlock(struct file *filp, struct file_lock *fl)
{
struct p9_getlock glock;
struct p9_fid *fid;
int res = 0;
fid = filp->private_data;
BUG_ON(fid == NULL);
posix_test_lock(filp, fl);
/*
* if we have a conflicting lock locally, no need to validate
* with server
*/
if (fl->fl_type != F_UNLCK)
return res;
/* convert posix lock to p9 tgetlock args */
memset(&glock, 0, sizeof(glock));
glock.type = P9_LOCK_TYPE_UNLCK;
glock.start = fl->fl_start;
if (fl->fl_end == OFFSET_MAX)
glock.length = 0;
else
glock.length = fl->fl_end - fl->fl_start + 1;
glock.proc_id = fl->fl_pid;
glock.client_id = fid->clnt->name;
res = p9_client_getlock_dotl(fid, &glock);
if (res < 0)
goto out;
/* map 9p lock type to os lock type */
switch (glock.type) {
case P9_LOCK_TYPE_RDLCK:
fl->fl_type = F_RDLCK;
break;
case P9_LOCK_TYPE_WRLCK:
fl->fl_type = F_WRLCK;
break;
case P9_LOCK_TYPE_UNLCK:
fl->fl_type = F_UNLCK;
break;
}
if (glock.type != P9_LOCK_TYPE_UNLCK) {
fl->fl_start = glock.start;
if (glock.length == 0)
fl->fl_end = OFFSET_MAX;
else
fl->fl_end = glock.start + glock.length - 1;
fl->fl_pid = glock.proc_id;
}
out:
if (glock.client_id != fid->clnt->name)
kfree(glock.client_id);
return res;
}
/**
* v9fs_file_lock_dotl - lock a file (or directory)
* @filp: file to be locked
* @cmd: lock command
* @fl: file lock structure
*
*/
static int v9fs_file_lock_dotl(struct file *filp, int cmd, struct file_lock *fl)
{
struct inode *inode = file_inode(filp);
int ret = -ENOLCK;
p9_debug(P9_DEBUG_VFS, "filp: %p cmd:%d lock: %p name: %pD\n",
filp, cmd, fl, filp);
/* No mandatory locks */
if (__mandatory_lock(inode) && fl->fl_type != F_UNLCK)
goto out_err;
if ((IS_SETLK(cmd) || IS_SETLKW(cmd)) && fl->fl_type != F_UNLCK) {
filemap_write_and_wait(inode->i_mapping);
invalidate_mapping_pages(&inode->i_data, 0, -1);
}
if (IS_SETLK(cmd) || IS_SETLKW(cmd))
ret = v9fs_file_do_lock(filp, cmd, fl);
else if (IS_GETLK(cmd))
ret = v9fs_file_getlock(filp, fl);
else
ret = -EINVAL;
out_err:
return ret;
}
/**
* v9fs_file_flock_dotl - lock a file
* @filp: file to be locked
* @cmd: lock command
* @fl: file lock structure
*
*/
static int v9fs_file_flock_dotl(struct file *filp, int cmd,
struct file_lock *fl)
{
struct inode *inode = file_inode(filp);
int ret = -ENOLCK;
p9_debug(P9_DEBUG_VFS, "filp: %p cmd:%d lock: %p name: %pD\n",
filp, cmd, fl, filp);
/* No mandatory locks */
if (__mandatory_lock(inode) && fl->fl_type != F_UNLCK)
goto out_err;
if (!(fl->fl_flags & FL_FLOCK))
goto out_err;
if ((IS_SETLK(cmd) || IS_SETLKW(cmd)) && fl->fl_type != F_UNLCK) {
filemap_write_and_wait(inode->i_mapping);
invalidate_mapping_pages(&inode->i_data, 0, -1);
}
/* Convert flock to posix lock */
fl->fl_flags |= FL_POSIX;
fl->fl_flags ^= FL_FLOCK;
if (IS_SETLK(cmd) | IS_SETLKW(cmd))
ret = v9fs_file_do_lock(filp, cmd, fl);
else
ret = -EINVAL;
out_err:
return ret;
}
/**
* v9fs_file_read - read from a file
* @filp: file pointer to read
* @udata: user data buffer to read data into
* @count: size of buffer
* @offset: offset at which to read data
*
*/
static ssize_t
v9fs_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
{
struct p9_fid *fid = iocb->ki_filp->private_data;
int ret, err = 0;
p9_debug(P9_DEBUG_VFS, "count %zu offset %lld\n",
iov_iter_count(to), iocb->ki_pos);
ret = p9_client_read(fid, iocb->ki_pos, to, &err);
if (!ret)
return err;
iocb->ki_pos += ret;
return ret;
}
/**
* v9fs_file_write - write to a file
* @filp: file pointer to write
* @data: data buffer to write data from
* @count: size of buffer
* @offset: offset at which to write data
*
*/
static ssize_t
v9fs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
struct file *file = iocb->ki_filp;
ssize_t retval;
loff_t origin;
int err = 0;
retval = generic_write_checks(iocb, from);
if (retval <= 0)
return retval;
origin = iocb->ki_pos;
retval = p9_client_write(file->private_data, iocb->ki_pos, from, &err);
if (retval > 0) {
struct inode *inode = file_inode(file);
loff_t i_size;
unsigned long pg_start, pg_end;
pg_start = origin >> PAGE_CACHE_SHIFT;
pg_end = (origin + retval - 1) >> PAGE_CACHE_SHIFT;
if (inode->i_mapping && inode->i_mapping->nrpages)
invalidate_inode_pages2_range(inode->i_mapping,
pg_start, pg_end);
iocb->ki_pos += retval;
i_size = i_size_read(inode);
if (iocb->ki_pos > i_size) {
inode_add_bytes(inode, iocb->ki_pos - i_size);
/*
* Need to serialize against i_size_write() in
* v9fs_stat2inode()
*/
v9fs_i_size_write(inode, iocb->ki_pos);
}
return retval;
}
return err;
}
static int v9fs_file_fsync(struct file *filp, loff_t start, loff_t end,
int datasync)
{
struct p9_fid *fid;
struct inode *inode = filp->f_mapping->host;
struct p9_wstat wstat;
int retval;
retval = filemap_write_and_wait_range(inode->i_mapping, start, end);
if (retval)
return retval;
mutex_lock(&inode->i_mutex);
p9_debug(P9_DEBUG_VFS, "filp %p datasync %x\n", filp, datasync);
fid = filp->private_data;
v9fs_blank_wstat(&wstat);
retval = p9_client_wstat(fid, &wstat);
mutex_unlock(&inode->i_mutex);
return retval;
}
int v9fs_file_fsync_dotl(struct file *filp, loff_t start, loff_t end,
int datasync)
{
struct p9_fid *fid;
struct inode *inode = filp->f_mapping->host;
int retval;
retval = filemap_write_and_wait_range(inode->i_mapping, start, end);
if (retval)
return retval;
mutex_lock(&inode->i_mutex);
p9_debug(P9_DEBUG_VFS, "filp %p datasync %x\n", filp, datasync);
fid = filp->private_data;
retval = p9_client_fsync(fid, datasync);
mutex_unlock(&inode->i_mutex);
return retval;
}
static int
v9fs_file_mmap(struct file *filp, struct vm_area_struct *vma)
{
int retval;
retval = generic_file_mmap(filp, vma);
if (!retval)
vma->vm_ops = &v9fs_file_vm_ops;
return retval;
}
static int
v9fs_mmap_file_mmap(struct file *filp, struct vm_area_struct *vma)
{
int retval;
struct inode *inode;
struct v9fs_inode *v9inode;
struct p9_fid *fid;
inode = file_inode(filp);
v9inode = V9FS_I(inode);
mutex_lock(&v9inode->v_mutex);
if (!v9inode->writeback_fid &&
(vma->vm_flags & VM_WRITE)) {
/*
* clone a fid and add it to writeback_fid
* we do it during mmap instead of
* page dirty time via write_begin/page_mkwrite
* because we want write after unlink usecase
* to work.
*/
fid = v9fs_writeback_fid(file_dentry(filp));
if (IS_ERR(fid)) {
retval = PTR_ERR(fid);
mutex_unlock(&v9inode->v_mutex);
return retval;
}
v9inode->writeback_fid = (void *) fid;
}
mutex_unlock(&v9inode->v_mutex);
retval = generic_file_mmap(filp, vma);
if (!retval)
vma->vm_ops = &v9fs_mmap_file_vm_ops;
return retval;
}
static int
v9fs_vm_page_mkwrite(struct vm_area_struct *vma, struct vm_fault *vmf)
{
struct v9fs_inode *v9inode;
struct page *page = vmf->page;
struct file *filp = vma->vm_file;
struct inode *inode = file_inode(filp);
p9_debug(P9_DEBUG_VFS, "page %p fid %lx\n",
page, (unsigned long)filp->private_data);
/* Update file times before taking page lock */
file_update_time(filp);
v9inode = V9FS_I(inode);
/* make sure the cache has finished storing the page */
v9fs_fscache_wait_on_page_write(inode, page);
BUG_ON(!v9inode->writeback_fid);
lock_page(page);
if (page->mapping != inode->i_mapping)
goto out_unlock;
wait_for_stable_page(page);
return VM_FAULT_LOCKED;
out_unlock:
unlock_page(page);
return VM_FAULT_NOPAGE;
}
/**
* v9fs_mmap_file_read - read from a file
* @filp: file pointer to read
* @data: user data buffer to read data into
* @count: size of buffer
* @offset: offset at which to read data
*
*/
static ssize_t
v9fs_mmap_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
{
/* TODO: Check if there are dirty pages */
return v9fs_file_read_iter(iocb, to);
}
/**
* v9fs_mmap_file_write - write to a file
* @filp: file pointer to write
* @data: data buffer to write data from
* @count: size of buffer
* @offset: offset at which to write data
*
*/
static ssize_t
v9fs_mmap_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
/*
* TODO: invalidate mmaps on filp's inode between
* offset and offset+count
*/
return v9fs_file_write_iter(iocb, from);
}
static void v9fs_mmap_vm_close(struct vm_area_struct *vma)
{
struct inode *inode;
struct writeback_control wbc = {
.nr_to_write = LONG_MAX,
.sync_mode = WB_SYNC_ALL,
.range_start = vma->vm_pgoff * PAGE_SIZE,
/* absolute end, byte at end included */
.range_end = vma->vm_pgoff * PAGE_SIZE +
(vma->vm_end - vma->vm_start - 1),
};
p9_debug(P9_DEBUG_VFS, "9p VMA close, %p, flushing", vma);
inode = file_inode(vma->vm_file);
if (!mapping_cap_writeback_dirty(inode->i_mapping))
wbc.nr_to_write = 0;
might_sleep();
sync_inode(inode, &wbc);
}
static const struct vm_operations_struct v9fs_file_vm_ops = {
.fault = filemap_fault,
.map_pages = filemap_map_pages,
.page_mkwrite = v9fs_vm_page_mkwrite,
};
static const struct vm_operations_struct v9fs_mmap_file_vm_ops = {
.close = v9fs_mmap_vm_close,
.fault = filemap_fault,
.map_pages = filemap_map_pages,
.page_mkwrite = v9fs_vm_page_mkwrite,
};
const struct file_operations v9fs_cached_file_operations = {
.llseek = generic_file_llseek,
.read_iter = generic_file_read_iter,
.write_iter = generic_file_write_iter,
.open = v9fs_file_open,
.release = v9fs_dir_release,
.lock = v9fs_file_lock,
.mmap = v9fs_file_mmap,
.fsync = v9fs_file_fsync,
};
const struct file_operations v9fs_cached_file_operations_dotl = {
.llseek = generic_file_llseek,
.read_iter = generic_file_read_iter,
.write_iter = generic_file_write_iter,
.open = v9fs_file_open,
.release = v9fs_dir_release,
.lock = v9fs_file_lock_dotl,
.flock = v9fs_file_flock_dotl,
.mmap = v9fs_file_mmap,
.fsync = v9fs_file_fsync_dotl,
};
const struct file_operations v9fs_file_operations = {
.llseek = generic_file_llseek,
.read_iter = v9fs_file_read_iter,
.write_iter = v9fs_file_write_iter,
.open = v9fs_file_open,
.release = v9fs_dir_release,
.lock = v9fs_file_lock,
.mmap = generic_file_readonly_mmap,
.fsync = v9fs_file_fsync,
};
const struct file_operations v9fs_file_operations_dotl = {
.llseek = generic_file_llseek,
.read_iter = v9fs_file_read_iter,
.write_iter = v9fs_file_write_iter,
.open = v9fs_file_open,
.release = v9fs_dir_release,
.lock = v9fs_file_lock_dotl,
.flock = v9fs_file_flock_dotl,
.mmap = generic_file_readonly_mmap,
.fsync = v9fs_file_fsync_dotl,
};
const struct file_operations v9fs_mmap_file_operations = {
.llseek = generic_file_llseek,
.read_iter = v9fs_mmap_file_read_iter,
.write_iter = v9fs_mmap_file_write_iter,
.open = v9fs_file_open,
.release = v9fs_dir_release,
.lock = v9fs_file_lock,
.mmap = v9fs_mmap_file_mmap,
.fsync = v9fs_file_fsync,
};
const struct file_operations v9fs_mmap_file_operations_dotl = {
.llseek = generic_file_llseek,
.read_iter = v9fs_mmap_file_read_iter,
.write_iter = v9fs_mmap_file_write_iter,
.open = v9fs_file_open,
.release = v9fs_dir_release,
.lock = v9fs_file_lock_dotl,
.flock = v9fs_file_flock_dotl,
.mmap = v9fs_mmap_file_mmap,
.fsync = v9fs_file_fsync_dotl,
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,999 @@
/*
* linux/fs/9p/vfs_inode_dotl.c
*
* This file contains vfs inode ops for the 9P2000.L protocol.
*
* Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
* Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/pagemap.h>
#include <linux/stat.h>
#include <linux/string.h>
#include <linux/inet.h>
#include <linux/namei.h>
#include <linux/idr.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/xattr.h>
#include <linux/posix_acl.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
#include "v9fs.h"
#include "v9fs_vfs.h"
#include "fid.h"
#include "cache.h"
#include "xattr.h"
#include "acl.h"
static int
v9fs_vfs_mknod_dotl(struct inode *dir, struct dentry *dentry, umode_t omode,
dev_t rdev);
/**
* v9fs_get_fsgid_for_create - Helper function to get the gid for creating a
* new file system object. This checks the S_ISGID to determine the owning
* group of the new file system object.
*/
static kgid_t v9fs_get_fsgid_for_create(struct inode *dir_inode)
{
BUG_ON(dir_inode == NULL);
if (dir_inode->i_mode & S_ISGID) {
/* set_gid bit is set.*/
return dir_inode->i_gid;
}
return current_fsgid();
}
static int v9fs_test_inode_dotl(struct inode *inode, void *data)
{
struct v9fs_inode *v9inode = V9FS_I(inode);
struct p9_stat_dotl *st = (struct p9_stat_dotl *)data;
/* don't match inode of different type */
if ((inode->i_mode & S_IFMT) != (st->st_mode & S_IFMT))
return 0;
if (inode->i_generation != st->st_gen)
return 0;
/* compare qid details */
if (memcmp(&v9inode->qid.version,
&st->qid.version, sizeof(v9inode->qid.version)))
return 0;
if (v9inode->qid.type != st->qid.type)
return 0;
if (v9inode->qid.path != st->qid.path)
return 0;
return 1;
}
/* Always get a new inode */
static int v9fs_test_new_inode_dotl(struct inode *inode, void *data)
{
return 0;
}
static int v9fs_set_inode_dotl(struct inode *inode, void *data)
{
struct v9fs_inode *v9inode = V9FS_I(inode);
struct p9_stat_dotl *st = (struct p9_stat_dotl *)data;
memcpy(&v9inode->qid, &st->qid, sizeof(st->qid));
inode->i_generation = st->st_gen;
return 0;
}
static struct inode *v9fs_qid_iget_dotl(struct super_block *sb,
struct p9_qid *qid,
struct p9_fid *fid,
struct p9_stat_dotl *st,
int new)
{
int retval;
unsigned long i_ino;
struct inode *inode;
struct v9fs_session_info *v9ses = sb->s_fs_info;
int (*test)(struct inode *, void *);
if (new)
test = v9fs_test_new_inode_dotl;
else
test = v9fs_test_inode_dotl;
i_ino = v9fs_qid2ino(qid);
inode = iget5_locked(sb, i_ino, test, v9fs_set_inode_dotl, st);
if (!inode)
return ERR_PTR(-ENOMEM);
if (!(inode->i_state & I_NEW))
return inode;
/*
* initialize the inode with the stat info
* FIXME!! we may need support for stale inodes
* later.
*/
inode->i_ino = i_ino;
retval = v9fs_init_inode(v9ses, inode,
st->st_mode, new_decode_dev(st->st_rdev));
if (retval)
goto error;
v9fs_stat2inode_dotl(st, inode, 0);
v9fs_cache_inode_get_cookie(inode);
retval = v9fs_get_acl(inode, fid);
if (retval)
goto error;
unlock_new_inode(inode);
return inode;
error:
iget_failed(inode);
return ERR_PTR(retval);
}
struct inode *
v9fs_inode_from_fid_dotl(struct v9fs_session_info *v9ses, struct p9_fid *fid,
struct super_block *sb, int new)
{
struct p9_stat_dotl *st;
struct inode *inode = NULL;
st = p9_client_getattr_dotl(fid, P9_STATS_BASIC | P9_STATS_GEN);
if (IS_ERR(st))
return ERR_CAST(st);
inode = v9fs_qid_iget_dotl(sb, &st->qid, fid, st, new);
kfree(st);
return inode;
}
struct dotl_openflag_map {
int open_flag;
int dotl_flag;
};
static int v9fs_mapped_dotl_flags(int flags)
{
int i;
int rflags = 0;
struct dotl_openflag_map dotl_oflag_map[] = {
{ O_CREAT, P9_DOTL_CREATE },
{ O_EXCL, P9_DOTL_EXCL },
{ O_NOCTTY, P9_DOTL_NOCTTY },
{ O_APPEND, P9_DOTL_APPEND },
{ O_NONBLOCK, P9_DOTL_NONBLOCK },
{ O_DSYNC, P9_DOTL_DSYNC },
{ FASYNC, P9_DOTL_FASYNC },
{ O_DIRECT, P9_DOTL_DIRECT },
{ O_LARGEFILE, P9_DOTL_LARGEFILE },
{ O_DIRECTORY, P9_DOTL_DIRECTORY },
{ O_NOFOLLOW, P9_DOTL_NOFOLLOW },
{ O_NOATIME, P9_DOTL_NOATIME },
{ O_CLOEXEC, P9_DOTL_CLOEXEC },
{ O_SYNC, P9_DOTL_SYNC},
};
for (i = 0; i < ARRAY_SIZE(dotl_oflag_map); i++) {
if (flags & dotl_oflag_map[i].open_flag)
rflags |= dotl_oflag_map[i].dotl_flag;
}
return rflags;
}
/**
* v9fs_open_to_dotl_flags- convert Linux specific open flags to
* plan 9 open flag.
* @flags: flags to convert
*/
int v9fs_open_to_dotl_flags(int flags)
{
int rflags = 0;
/*
* We have same bits for P9_DOTL_READONLY, P9_DOTL_WRONLY
* and P9_DOTL_NOACCESS
*/
rflags |= flags & O_ACCMODE;
rflags |= v9fs_mapped_dotl_flags(flags);
return rflags;
}
/**
* v9fs_vfs_create_dotl - VFS hook to create files for 9P2000.L protocol.
* @dir: directory inode that is being created
* @dentry: dentry that is being deleted
* @omode: create permissions
*
*/
static int
v9fs_vfs_create_dotl(struct inode *dir, struct dentry *dentry, umode_t omode,
bool excl)
{
return v9fs_vfs_mknod_dotl(dir, dentry, omode, 0);
}
static int
v9fs_vfs_atomic_open_dotl(struct inode *dir, struct dentry *dentry,
struct file *file, unsigned flags, umode_t omode,
int *opened)
{
int err = 0;
kgid_t gid;
umode_t mode;
char *name = NULL;
struct p9_qid qid;
struct inode *inode;
struct p9_fid *fid = NULL;
struct v9fs_inode *v9inode;
struct p9_fid *dfid, *ofid, *inode_fid;
struct v9fs_session_info *v9ses;
struct posix_acl *pacl = NULL, *dacl = NULL;
struct dentry *res = NULL;
if (d_unhashed(dentry)) {
res = v9fs_vfs_lookup(dir, dentry, 0);
if (IS_ERR(res))
return PTR_ERR(res);
if (res)
dentry = res;
}
/* Only creates */
if (!(flags & O_CREAT) || d_really_is_positive(dentry))
return finish_no_open(file, res);
v9ses = v9fs_inode2v9ses(dir);
name = (char *) dentry->d_name.name;
p9_debug(P9_DEBUG_VFS, "name:%s flags:0x%x mode:0x%hx\n",
name, flags, omode);
dfid = v9fs_fid_lookup(dentry->d_parent);
if (IS_ERR(dfid)) {
err = PTR_ERR(dfid);
p9_debug(P9_DEBUG_VFS, "fid lookup failed %d\n", err);
goto out;
}
/* clone a fid to use for creation */
ofid = p9_client_walk(dfid, 0, NULL, 1);
if (IS_ERR(ofid)) {
err = PTR_ERR(ofid);
p9_debug(P9_DEBUG_VFS, "p9_client_walk failed %d\n", err);
goto out;
}
gid = v9fs_get_fsgid_for_create(dir);
mode = omode;
/* Update mode based on ACL value */
err = v9fs_acl_mode(dir, &mode, &dacl, &pacl);
if (err) {
p9_debug(P9_DEBUG_VFS, "Failed to get acl values in creat %d\n",
err);
goto error;
}
err = p9_client_create_dotl(ofid, name, v9fs_open_to_dotl_flags(flags),
mode, gid, &qid);
if (err < 0) {
p9_debug(P9_DEBUG_VFS, "p9_client_open_dotl failed in creat %d\n",
err);
goto error;
}
v9fs_invalidate_inode_attr(dir);
/* instantiate inode and assign the unopened fid to the dentry */
fid = p9_client_walk(dfid, 1, &name, 1);
if (IS_ERR(fid)) {
err = PTR_ERR(fid);
p9_debug(P9_DEBUG_VFS, "p9_client_walk failed %d\n", err);
fid = NULL;
goto error;
}
inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb);
if (IS_ERR(inode)) {
err = PTR_ERR(inode);
p9_debug(P9_DEBUG_VFS, "inode creation failed %d\n", err);
goto error;
}
/* Now set the ACL based on the default value */
v9fs_set_create_acl(inode, fid, dacl, pacl);
v9fs_fid_add(dentry, fid);
d_instantiate(dentry, inode);
v9inode = V9FS_I(inode);
mutex_lock(&v9inode->v_mutex);
if ((v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) &&
!v9inode->writeback_fid &&
((flags & O_ACCMODE) != O_RDONLY)) {
/*
* clone a fid and add it to writeback_fid
* we do it during open time instead of
* page dirty time via write_begin/page_mkwrite
* because we want write after unlink usecase
* to work.
*/
inode_fid = v9fs_writeback_fid(dentry);
if (IS_ERR(inode_fid)) {
err = PTR_ERR(inode_fid);
mutex_unlock(&v9inode->v_mutex);
goto err_clunk_old_fid;
}
v9inode->writeback_fid = (void *) inode_fid;
}
mutex_unlock(&v9inode->v_mutex);
/* Since we are opening a file, assign the open fid to the file */
err = finish_open(file, dentry, generic_file_open, opened);
if (err)
goto err_clunk_old_fid;
file->private_data = ofid;
if (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE)
v9fs_cache_inode_set_cookie(inode, file);
*opened |= FILE_CREATED;
out:
v9fs_put_acl(dacl, pacl);
dput(res);
return err;
error:
if (fid)
p9_client_clunk(fid);
err_clunk_old_fid:
if (ofid)
p9_client_clunk(ofid);
goto out;
}
/**
* v9fs_vfs_mkdir_dotl - VFS mkdir hook to create a directory
* @dir: inode that is being unlinked
* @dentry: dentry that is being unlinked
* @omode: mode for new directory
*
*/
static int v9fs_vfs_mkdir_dotl(struct inode *dir,
struct dentry *dentry, umode_t omode)
{
int err;
struct v9fs_session_info *v9ses;
struct p9_fid *fid = NULL, *dfid = NULL;
kgid_t gid;
char *name;
umode_t mode;
struct inode *inode;
struct p9_qid qid;
struct dentry *dir_dentry;
struct posix_acl *dacl = NULL, *pacl = NULL;
p9_debug(P9_DEBUG_VFS, "name %pd\n", dentry);
err = 0;
v9ses = v9fs_inode2v9ses(dir);
omode |= S_IFDIR;
if (dir->i_mode & S_ISGID)
omode |= S_ISGID;
dir_dentry = dentry->d_parent;
dfid = v9fs_fid_lookup(dir_dentry);
if (IS_ERR(dfid)) {
err = PTR_ERR(dfid);
p9_debug(P9_DEBUG_VFS, "fid lookup failed %d\n", err);
dfid = NULL;
goto error;
}
gid = v9fs_get_fsgid_for_create(dir);
mode = omode;
/* Update mode based on ACL value */
err = v9fs_acl_mode(dir, &mode, &dacl, &pacl);
if (err) {
p9_debug(P9_DEBUG_VFS, "Failed to get acl values in mkdir %d\n",
err);
goto error;
}
name = (char *) dentry->d_name.name;
err = p9_client_mkdir_dotl(dfid, name, mode, gid, &qid);
if (err < 0)
goto error;
fid = p9_client_walk(dfid, 1, &name, 1);
if (IS_ERR(fid)) {
err = PTR_ERR(fid);
p9_debug(P9_DEBUG_VFS, "p9_client_walk failed %d\n",
err);
fid = NULL;
goto error;
}
/* instantiate inode and assign the unopened fid to the dentry */
if (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) {
inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb);
if (IS_ERR(inode)) {
err = PTR_ERR(inode);
p9_debug(P9_DEBUG_VFS, "inode creation failed %d\n",
err);
goto error;
}
v9fs_fid_add(dentry, fid);
v9fs_set_create_acl(inode, fid, dacl, pacl);
d_instantiate(dentry, inode);
fid = NULL;
err = 0;
} else {
/*
* Not in cached mode. No need to populate
* inode with stat. We need to get an inode
* so that we can set the acl with dentry
*/
inode = v9fs_get_inode(dir->i_sb, mode, 0);
if (IS_ERR(inode)) {
err = PTR_ERR(inode);
goto error;
}
v9fs_set_create_acl(inode, fid, dacl, pacl);
d_instantiate(dentry, inode);
}
inc_nlink(dir);
v9fs_invalidate_inode_attr(dir);
error:
if (fid)
p9_client_clunk(fid);
v9fs_put_acl(dacl, pacl);
return err;
}
static int
v9fs_vfs_getattr_dotl(struct vfsmount *mnt, struct dentry *dentry,
struct kstat *stat)
{
struct v9fs_session_info *v9ses;
struct p9_fid *fid;
struct p9_stat_dotl *st;
p9_debug(P9_DEBUG_VFS, "dentry: %p\n", dentry);
v9ses = v9fs_dentry2v9ses(dentry);
if (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) {
generic_fillattr(d_inode(dentry), stat);
return 0;
}
fid = v9fs_fid_lookup(dentry);
if (IS_ERR(fid))
return PTR_ERR(fid);
/* Ask for all the fields in stat structure. Server will return
* whatever it supports
*/
st = p9_client_getattr_dotl(fid, P9_STATS_ALL);
if (IS_ERR(st))
return PTR_ERR(st);
v9fs_stat2inode_dotl(st, d_inode(dentry), 0);
generic_fillattr(d_inode(dentry), stat);
/* Change block size to what the server returned */
stat->blksize = st->st_blksize;
kfree(st);
return 0;
}
/*
* Attribute flags.
*/
#define P9_ATTR_MODE (1 << 0)
#define P9_ATTR_UID (1 << 1)
#define P9_ATTR_GID (1 << 2)
#define P9_ATTR_SIZE (1 << 3)
#define P9_ATTR_ATIME (1 << 4)
#define P9_ATTR_MTIME (1 << 5)
#define P9_ATTR_CTIME (1 << 6)
#define P9_ATTR_ATIME_SET (1 << 7)
#define P9_ATTR_MTIME_SET (1 << 8)
struct dotl_iattr_map {
int iattr_valid;
int p9_iattr_valid;
};
static int v9fs_mapped_iattr_valid(int iattr_valid)
{
int i;
int p9_iattr_valid = 0;
struct dotl_iattr_map dotl_iattr_map[] = {
{ ATTR_MODE, P9_ATTR_MODE },
{ ATTR_UID, P9_ATTR_UID },
{ ATTR_GID, P9_ATTR_GID },
{ ATTR_SIZE, P9_ATTR_SIZE },
{ ATTR_ATIME, P9_ATTR_ATIME },
{ ATTR_MTIME, P9_ATTR_MTIME },
{ ATTR_CTIME, P9_ATTR_CTIME },
{ ATTR_ATIME_SET, P9_ATTR_ATIME_SET },
{ ATTR_MTIME_SET, P9_ATTR_MTIME_SET },
};
for (i = 0; i < ARRAY_SIZE(dotl_iattr_map); i++) {
if (iattr_valid & dotl_iattr_map[i].iattr_valid)
p9_iattr_valid |= dotl_iattr_map[i].p9_iattr_valid;
}
return p9_iattr_valid;
}
/**
* v9fs_vfs_setattr_dotl - set file metadata
* @dentry: file whose metadata to set
* @iattr: metadata assignment structure
*
*/
int v9fs_vfs_setattr_dotl(struct dentry *dentry, struct iattr *iattr)
{
int retval;
struct p9_fid *fid;
struct p9_iattr_dotl p9attr;
struct inode *inode = d_inode(dentry);
p9_debug(P9_DEBUG_VFS, "\n");
retval = inode_change_ok(inode, iattr);
if (retval)
return retval;
p9attr.valid = v9fs_mapped_iattr_valid(iattr->ia_valid);
p9attr.mode = iattr->ia_mode;
p9attr.uid = iattr->ia_uid;
p9attr.gid = iattr->ia_gid;
p9attr.size = iattr->ia_size;
p9attr.atime_sec = iattr->ia_atime.tv_sec;
p9attr.atime_nsec = iattr->ia_atime.tv_nsec;
p9attr.mtime_sec = iattr->ia_mtime.tv_sec;
p9attr.mtime_nsec = iattr->ia_mtime.tv_nsec;
fid = v9fs_fid_lookup(dentry);
if (IS_ERR(fid))
return PTR_ERR(fid);
/* Write all dirty data */
if (S_ISREG(inode->i_mode))
filemap_write_and_wait(inode->i_mapping);
retval = p9_client_setattr(fid, &p9attr);
if (retval < 0)
return retval;
if ((iattr->ia_valid & ATTR_SIZE) &&
iattr->ia_size != i_size_read(inode))
truncate_setsize(inode, iattr->ia_size);
v9fs_invalidate_inode_attr(inode);
setattr_copy(inode, iattr);
mark_inode_dirty(inode);
if (iattr->ia_valid & ATTR_MODE) {
/* We also want to update ACL when we update mode bits */
retval = v9fs_acl_chmod(inode, fid);
if (retval < 0)
return retval;
}
return 0;
}
/**
* v9fs_stat2inode_dotl - populate an inode structure with stat info
* @stat: stat structure
* @inode: inode to populate
* @flags: ctrl flags (e.g. V9FS_STAT2INODE_KEEP_ISIZE)
*
*/
void
v9fs_stat2inode_dotl(struct p9_stat_dotl *stat, struct inode *inode,
unsigned int flags)
{
umode_t mode;
struct v9fs_inode *v9inode = V9FS_I(inode);
if ((stat->st_result_mask & P9_STATS_BASIC) == P9_STATS_BASIC) {
inode->i_atime.tv_sec = stat->st_atime_sec;
inode->i_atime.tv_nsec = stat->st_atime_nsec;
inode->i_mtime.tv_sec = stat->st_mtime_sec;
inode->i_mtime.tv_nsec = stat->st_mtime_nsec;
inode->i_ctime.tv_sec = stat->st_ctime_sec;
inode->i_ctime.tv_nsec = stat->st_ctime_nsec;
inode->i_uid = stat->st_uid;
inode->i_gid = stat->st_gid;
set_nlink(inode, stat->st_nlink);
mode = stat->st_mode & S_IALLUGO;
mode |= inode->i_mode & ~S_IALLUGO;
inode->i_mode = mode;
if (!(flags & V9FS_STAT2INODE_KEEP_ISIZE))
v9fs_i_size_write(inode, stat->st_size);
inode->i_blocks = stat->st_blocks;
} else {
if (stat->st_result_mask & P9_STATS_ATIME) {
inode->i_atime.tv_sec = stat->st_atime_sec;
inode->i_atime.tv_nsec = stat->st_atime_nsec;
}
if (stat->st_result_mask & P9_STATS_MTIME) {
inode->i_mtime.tv_sec = stat->st_mtime_sec;
inode->i_mtime.tv_nsec = stat->st_mtime_nsec;
}
if (stat->st_result_mask & P9_STATS_CTIME) {
inode->i_ctime.tv_sec = stat->st_ctime_sec;
inode->i_ctime.tv_nsec = stat->st_ctime_nsec;
}
if (stat->st_result_mask & P9_STATS_UID)
inode->i_uid = stat->st_uid;
if (stat->st_result_mask & P9_STATS_GID)
inode->i_gid = stat->st_gid;
if (stat->st_result_mask & P9_STATS_NLINK)
set_nlink(inode, stat->st_nlink);
if (stat->st_result_mask & P9_STATS_MODE) {
inode->i_mode = stat->st_mode;
if ((S_ISBLK(inode->i_mode)) ||
(S_ISCHR(inode->i_mode)))
init_special_inode(inode, inode->i_mode,
inode->i_rdev);
}
if (stat->st_result_mask & P9_STATS_RDEV)
inode->i_rdev = new_decode_dev(stat->st_rdev);
if (!(flags & V9FS_STAT2INODE_KEEP_ISIZE) &&
stat->st_result_mask & P9_STATS_SIZE)
v9fs_i_size_write(inode, stat->st_size);
if (stat->st_result_mask & P9_STATS_BLOCKS)
inode->i_blocks = stat->st_blocks;
}
if (stat->st_result_mask & P9_STATS_GEN)
inode->i_generation = stat->st_gen;
/* Currently we don't support P9_STATS_BTIME and P9_STATS_DATA_VERSION
* because the inode structure does not have fields for them.
*/
v9inode->cache_validity &= ~V9FS_INO_INVALID_ATTR;
}
static int
v9fs_vfs_symlink_dotl(struct inode *dir, struct dentry *dentry,
const char *symname)
{
int err;
kgid_t gid;
char *name;
struct p9_qid qid;
struct inode *inode;
struct p9_fid *dfid;
struct p9_fid *fid = NULL;
struct v9fs_session_info *v9ses;
name = (char *) dentry->d_name.name;
p9_debug(P9_DEBUG_VFS, "%lu,%s,%s\n", dir->i_ino, name, symname);
v9ses = v9fs_inode2v9ses(dir);
dfid = v9fs_fid_lookup(dentry->d_parent);
if (IS_ERR(dfid)) {
err = PTR_ERR(dfid);
p9_debug(P9_DEBUG_VFS, "fid lookup failed %d\n", err);
return err;
}
gid = v9fs_get_fsgid_for_create(dir);
/* Server doesn't alter fid on TSYMLINK. Hence no need to clone it. */
err = p9_client_symlink(dfid, name, (char *)symname, gid, &qid);
if (err < 0) {
p9_debug(P9_DEBUG_VFS, "p9_client_symlink failed %d\n", err);
goto error;
}
v9fs_invalidate_inode_attr(dir);
if (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) {
/* Now walk from the parent so we can get an unopened fid. */
fid = p9_client_walk(dfid, 1, &name, 1);
if (IS_ERR(fid)) {
err = PTR_ERR(fid);
p9_debug(P9_DEBUG_VFS, "p9_client_walk failed %d\n",
err);
fid = NULL;
goto error;
}
/* instantiate inode and assign the unopened fid to dentry */
inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb);
if (IS_ERR(inode)) {
err = PTR_ERR(inode);
p9_debug(P9_DEBUG_VFS, "inode creation failed %d\n",
err);
goto error;
}
v9fs_fid_add(dentry, fid);
d_instantiate(dentry, inode);
fid = NULL;
err = 0;
} else {
/* Not in cached mode. No need to populate inode with stat */
inode = v9fs_get_inode(dir->i_sb, S_IFLNK, 0);
if (IS_ERR(inode)) {
err = PTR_ERR(inode);
goto error;
}
d_instantiate(dentry, inode);
}
error:
if (fid)
p9_client_clunk(fid);
return err;
}
/**
* v9fs_vfs_link_dotl - create a hardlink for dotl
* @old_dentry: dentry for file to link to
* @dir: inode destination for new link
* @dentry: dentry for link
*
*/
static int
v9fs_vfs_link_dotl(struct dentry *old_dentry, struct inode *dir,
struct dentry *dentry)
{
int err;
struct dentry *dir_dentry;
struct p9_fid *dfid, *oldfid;
struct v9fs_session_info *v9ses;
p9_debug(P9_DEBUG_VFS, "dir ino: %lu, old_name: %pd, new_name: %pd\n",
dir->i_ino, old_dentry, dentry);
v9ses = v9fs_inode2v9ses(dir);
dir_dentry = dentry->d_parent;
dfid = v9fs_fid_lookup(dir_dentry);
if (IS_ERR(dfid))
return PTR_ERR(dfid);
oldfid = v9fs_fid_lookup(old_dentry);
if (IS_ERR(oldfid))
return PTR_ERR(oldfid);
err = p9_client_link(dfid, oldfid, (char *)dentry->d_name.name);
if (err < 0) {
p9_debug(P9_DEBUG_VFS, "p9_client_link failed %d\n", err);
return err;
}
v9fs_invalidate_inode_attr(dir);
if (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) {
/* Get the latest stat info from server. */
struct p9_fid *fid;
fid = v9fs_fid_lookup(old_dentry);
if (IS_ERR(fid))
return PTR_ERR(fid);
v9fs_refresh_inode_dotl(fid, d_inode(old_dentry));
}
ihold(d_inode(old_dentry));
d_instantiate(dentry, d_inode(old_dentry));
return err;
}
/**
* v9fs_vfs_mknod_dotl - create a special file
* @dir: inode destination for new link
* @dentry: dentry for file
* @omode: mode for creation
* @rdev: device associated with special file
*
*/
static int
v9fs_vfs_mknod_dotl(struct inode *dir, struct dentry *dentry, umode_t omode,
dev_t rdev)
{
int err;
kgid_t gid;
char *name;
umode_t mode;
struct v9fs_session_info *v9ses;
struct p9_fid *fid = NULL, *dfid = NULL;
struct inode *inode;
struct p9_qid qid;
struct dentry *dir_dentry;
struct posix_acl *dacl = NULL, *pacl = NULL;
p9_debug(P9_DEBUG_VFS, " %lu,%pd mode: %hx MAJOR: %u MINOR: %u\n",
dir->i_ino, dentry, omode,
MAJOR(rdev), MINOR(rdev));
v9ses = v9fs_inode2v9ses(dir);
dir_dentry = dentry->d_parent;
dfid = v9fs_fid_lookup(dir_dentry);
if (IS_ERR(dfid)) {
err = PTR_ERR(dfid);
p9_debug(P9_DEBUG_VFS, "fid lookup failed %d\n", err);
dfid = NULL;
goto error;
}
gid = v9fs_get_fsgid_for_create(dir);
mode = omode;
/* Update mode based on ACL value */
err = v9fs_acl_mode(dir, &mode, &dacl, &pacl);
if (err) {
p9_debug(P9_DEBUG_VFS, "Failed to get acl values in mknod %d\n",
err);
goto error;
}
name = (char *) dentry->d_name.name;
err = p9_client_mknod_dotl(dfid, name, mode, rdev, gid, &qid);
if (err < 0)
goto error;
v9fs_invalidate_inode_attr(dir);
fid = p9_client_walk(dfid, 1, &name, 1);
if (IS_ERR(fid)) {
err = PTR_ERR(fid);
p9_debug(P9_DEBUG_VFS, "p9_client_walk failed %d\n",
err);
fid = NULL;
goto error;
}
/* instantiate inode and assign the unopened fid to the dentry */
if (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) {
inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb);
if (IS_ERR(inode)) {
err = PTR_ERR(inode);
p9_debug(P9_DEBUG_VFS, "inode creation failed %d\n",
err);
goto error;
}
v9fs_set_create_acl(inode, fid, dacl, pacl);
v9fs_fid_add(dentry, fid);
d_instantiate(dentry, inode);
fid = NULL;
err = 0;
} else {
/*
* Not in cached mode. No need to populate inode with stat.
* socket syscall returns a fd, so we need instantiate
*/
inode = v9fs_get_inode(dir->i_sb, mode, rdev);
if (IS_ERR(inode)) {
err = PTR_ERR(inode);
goto error;
}
v9fs_set_create_acl(inode, fid, dacl, pacl);
d_instantiate(dentry, inode);
}
error:
if (fid)
p9_client_clunk(fid);
v9fs_put_acl(dacl, pacl);
return err;
}
/**
* v9fs_vfs_follow_link_dotl - follow a symlink path
* @dentry: dentry for symlink
* @cookie: place to pass the data to put_link()
*/
static const char *
v9fs_vfs_follow_link_dotl(struct dentry *dentry, void **cookie)
{
struct p9_fid *fid = v9fs_fid_lookup(dentry);
char *target;
int retval;
p9_debug(P9_DEBUG_VFS, "%pd\n", dentry);
if (IS_ERR(fid))
return ERR_CAST(fid);
retval = p9_client_readlink(fid, &target);
if (retval)
return ERR_PTR(retval);
return *cookie = target;
}
int v9fs_refresh_inode_dotl(struct p9_fid *fid, struct inode *inode)
{
struct p9_stat_dotl *st;
struct v9fs_session_info *v9ses;
unsigned int flags;
v9ses = v9fs_inode2v9ses(inode);
st = p9_client_getattr_dotl(fid, P9_STATS_ALL);
if (IS_ERR(st))
return PTR_ERR(st);
/*
* Don't update inode if the file type is different
*/
if ((inode->i_mode & S_IFMT) != (st->st_mode & S_IFMT))
goto out;
/*
* We don't want to refresh inode->i_size,
* because we may have cached data
*/
flags = (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) ?
V9FS_STAT2INODE_KEEP_ISIZE : 0;
v9fs_stat2inode_dotl(st, inode, flags);
out:
kfree(st);
return 0;
}
const struct inode_operations v9fs_dir_inode_operations_dotl = {
.create = v9fs_vfs_create_dotl,
.atomic_open = v9fs_vfs_atomic_open_dotl,
.lookup = v9fs_vfs_lookup,
.link = v9fs_vfs_link_dotl,
.symlink = v9fs_vfs_symlink_dotl,
.unlink = v9fs_vfs_unlink,
.mkdir = v9fs_vfs_mkdir_dotl,
.rmdir = v9fs_vfs_rmdir,
.mknod = v9fs_vfs_mknod_dotl,
.rename = v9fs_vfs_rename,
.getattr = v9fs_vfs_getattr_dotl,
.setattr = v9fs_vfs_setattr_dotl,
.setxattr = generic_setxattr,
.getxattr = generic_getxattr,
.removexattr = generic_removexattr,
.listxattr = v9fs_listxattr,
.get_acl = v9fs_iop_get_acl,
};
const struct inode_operations v9fs_file_inode_operations_dotl = {
.getattr = v9fs_vfs_getattr_dotl,
.setattr = v9fs_vfs_setattr_dotl,
.setxattr = generic_setxattr,
.getxattr = generic_getxattr,
.removexattr = generic_removexattr,
.listxattr = v9fs_listxattr,
.get_acl = v9fs_iop_get_acl,
};
const struct inode_operations v9fs_symlink_inode_operations_dotl = {
.readlink = generic_readlink,
.follow_link = v9fs_vfs_follow_link_dotl,
.put_link = kfree_put_link,
.getattr = v9fs_vfs_getattr_dotl,
.setattr = v9fs_vfs_setattr_dotl,
.setxattr = generic_setxattr,
.getxattr = generic_getxattr,
.removexattr = generic_removexattr,
.listxattr = v9fs_listxattr,
};

View File

@@ -0,0 +1,366 @@
/*
* linux/fs/9p/vfs_super.c
*
* This file contians superblock ops for 9P2000. It is intended that
* you mount this file system on directories.
*
* Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
* Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/stat.h>
#include <linux/string.h>
#include <linux/inet.h>
#include <linux/pagemap.h>
#include <linux/seq_file.h>
#include <linux/mount.h>
#include <linux/idr.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/statfs.h>
#include <linux/magic.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
#include "v9fs.h"
#include "v9fs_vfs.h"
#include "fid.h"
#include "xattr.h"
#include "acl.h"
static const struct super_operations v9fs_super_ops, v9fs_super_ops_dotl;
/**
* v9fs_set_super - set the superblock
* @s: super block
* @data: file system specific data
*
*/
static int v9fs_set_super(struct super_block *s, void *data)
{
s->s_fs_info = data;
return set_anon_super(s, data);
}
/**
* v9fs_fill_super - populate superblock with info
* @sb: superblock
* @v9ses: session information
* @flags: flags propagated from v9fs_mount()
*
*/
static void
v9fs_fill_super(struct super_block *sb, struct v9fs_session_info *v9ses,
int flags, void *data)
{
sb->s_maxbytes = MAX_LFS_FILESIZE;
sb->s_blocksize_bits = fls(v9ses->maxdata - 1);
sb->s_blocksize = 1 << sb->s_blocksize_bits;
sb->s_magic = V9FS_MAGIC;
if (v9fs_proto_dotl(v9ses)) {
sb->s_op = &v9fs_super_ops_dotl;
sb->s_xattr = v9fs_xattr_handlers;
} else
sb->s_op = &v9fs_super_ops;
sb->s_bdi = &v9ses->bdi;
if (v9ses->cache)
sb->s_bdi->ra_pages = (VM_MAX_READAHEAD * 1024)/PAGE_CACHE_SIZE;
sb->s_flags |= MS_ACTIVE | MS_DIRSYNC | MS_NOATIME;
if (!v9ses->cache)
sb->s_flags |= MS_SYNCHRONOUS;
#ifdef CONFIG_9P_FS_POSIX_ACL
if ((v9ses->flags & V9FS_ACL_MASK) == V9FS_POSIX_ACL)
sb->s_flags |= MS_POSIXACL;
#endif
save_mount_options(sb, data);
}
/**
* v9fs_mount - mount a superblock
* @fs_type: file system type
* @flags: mount flags
* @dev_name: device name that was mounted
* @data: mount options
*
*/
static struct dentry *v9fs_mount(struct file_system_type *fs_type, int flags,
const char *dev_name, void *data)
{
struct super_block *sb = NULL;
struct inode *inode = NULL;
struct dentry *root = NULL;
struct v9fs_session_info *v9ses = NULL;
umode_t mode = S_IRWXUGO | S_ISVTX;
struct p9_fid *fid;
int retval = 0;
p9_debug(P9_DEBUG_VFS, "\n");
v9ses = kzalloc(sizeof(struct v9fs_session_info), GFP_KERNEL);
if (!v9ses)
return ERR_PTR(-ENOMEM);
fid = v9fs_session_init(v9ses, dev_name, data);
if (IS_ERR(fid)) {
retval = PTR_ERR(fid);
goto free_session;
}
sb = sget(fs_type, NULL, v9fs_set_super, flags, v9ses);
if (IS_ERR(sb)) {
retval = PTR_ERR(sb);
goto clunk_fid;
}
v9fs_fill_super(sb, v9ses, flags, data);
if (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE)
sb->s_d_op = &v9fs_cached_dentry_operations;
else
sb->s_d_op = &v9fs_dentry_operations;
inode = v9fs_get_inode(sb, S_IFDIR | mode, 0);
if (IS_ERR(inode)) {
retval = PTR_ERR(inode);
goto release_sb;
}
root = d_make_root(inode);
if (!root) {
retval = -ENOMEM;
goto release_sb;
}
sb->s_root = root;
if (v9fs_proto_dotl(v9ses)) {
struct p9_stat_dotl *st = NULL;
st = p9_client_getattr_dotl(fid, P9_STATS_BASIC);
if (IS_ERR(st)) {
retval = PTR_ERR(st);
goto release_sb;
}
d_inode(root)->i_ino = v9fs_qid2ino(&st->qid);
v9fs_stat2inode_dotl(st, d_inode(root), 0);
kfree(st);
} else {
struct p9_wstat *st = NULL;
st = p9_client_stat(fid);
if (IS_ERR(st)) {
retval = PTR_ERR(st);
goto release_sb;
}
d_inode(root)->i_ino = v9fs_qid2ino(&st->qid);
v9fs_stat2inode(st, d_inode(root), sb, 0);
p9stat_free(st);
kfree(st);
}
retval = v9fs_get_acl(inode, fid);
if (retval)
goto release_sb;
v9fs_fid_add(root, fid);
p9_debug(P9_DEBUG_VFS, " simple set mount, return 0\n");
return dget(sb->s_root);
clunk_fid:
p9_client_clunk(fid);
v9fs_session_close(v9ses);
free_session:
kfree(v9ses);
return ERR_PTR(retval);
release_sb:
/*
* we will do the session_close and root dentry release
* in the below call. But we need to clunk fid, because we haven't
* attached the fid to dentry so it won't get clunked
* automatically.
*/
p9_client_clunk(fid);
deactivate_locked_super(sb);
return ERR_PTR(retval);
}
/**
* v9fs_kill_super - Kill Superblock
* @s: superblock
*
*/
static void v9fs_kill_super(struct super_block *s)
{
struct v9fs_session_info *v9ses = s->s_fs_info;
p9_debug(P9_DEBUG_VFS, " %p\n", s);
kill_anon_super(s);
v9fs_session_cancel(v9ses);
v9fs_session_close(v9ses);
kfree(v9ses);
s->s_fs_info = NULL;
p9_debug(P9_DEBUG_VFS, "exiting kill_super\n");
}
static void
v9fs_umount_begin(struct super_block *sb)
{
struct v9fs_session_info *v9ses;
v9ses = sb->s_fs_info;
v9fs_session_begin_cancel(v9ses);
}
static int v9fs_statfs(struct dentry *dentry, struct kstatfs *buf)
{
struct v9fs_session_info *v9ses;
struct p9_fid *fid;
struct p9_rstatfs rs;
int res;
fid = v9fs_fid_lookup(dentry);
if (IS_ERR(fid)) {
res = PTR_ERR(fid);
goto done;
}
v9ses = v9fs_dentry2v9ses(dentry);
if (v9fs_proto_dotl(v9ses)) {
res = p9_client_statfs(fid, &rs);
if (res == 0) {
buf->f_type = rs.type;
buf->f_bsize = rs.bsize;
buf->f_blocks = rs.blocks;
buf->f_bfree = rs.bfree;
buf->f_bavail = rs.bavail;
buf->f_files = rs.files;
buf->f_ffree = rs.ffree;
buf->f_fsid.val[0] = rs.fsid & 0xFFFFFFFFUL;
buf->f_fsid.val[1] = (rs.fsid >> 32) & 0xFFFFFFFFUL;
buf->f_namelen = rs.namelen;
}
if (res != -ENOSYS)
goto done;
}
res = simple_statfs(dentry, buf);
done:
return res;
}
static int v9fs_drop_inode(struct inode *inode)
{
struct v9fs_session_info *v9ses;
v9ses = v9fs_inode2v9ses(inode);
if (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE)
return generic_drop_inode(inode);
/*
* in case of non cached mode always drop the
* the inode because we want the inode attribute
* to always match that on the server.
*/
return 1;
}
static int v9fs_write_inode(struct inode *inode,
struct writeback_control *wbc)
{
int ret;
struct p9_wstat wstat;
struct v9fs_inode *v9inode;
/*
* send an fsync request to server irrespective of
* wbc->sync_mode.
*/
p9_debug(P9_DEBUG_VFS, "%s: inode %p\n", __func__, inode);
v9inode = V9FS_I(inode);
if (!v9inode->writeback_fid)
return 0;
v9fs_blank_wstat(&wstat);
ret = p9_client_wstat(v9inode->writeback_fid, &wstat);
if (ret < 0) {
__mark_inode_dirty(inode, I_DIRTY_DATASYNC);
return ret;
}
return 0;
}
static int v9fs_write_inode_dotl(struct inode *inode,
struct writeback_control *wbc)
{
int ret;
struct v9fs_inode *v9inode;
/*
* send an fsync request to server irrespective of
* wbc->sync_mode.
*/
v9inode = V9FS_I(inode);
p9_debug(P9_DEBUG_VFS, "%s: inode %p, writeback_fid %p\n",
__func__, inode, v9inode->writeback_fid);
if (!v9inode->writeback_fid)
return 0;
ret = p9_client_fsync(v9inode->writeback_fid, 0);
if (ret < 0) {
__mark_inode_dirty(inode, I_DIRTY_DATASYNC);
return ret;
}
return 0;
}
static const struct super_operations v9fs_super_ops = {
.alloc_inode = v9fs_alloc_inode,
.destroy_inode = v9fs_destroy_inode,
.statfs = simple_statfs,
.evict_inode = v9fs_evict_inode,
.show_options = generic_show_options,
.umount_begin = v9fs_umount_begin,
.write_inode = v9fs_write_inode,
};
static const struct super_operations v9fs_super_ops_dotl = {
.alloc_inode = v9fs_alloc_inode,
.destroy_inode = v9fs_destroy_inode,
.statfs = v9fs_statfs,
.drop_inode = v9fs_drop_inode,
.evict_inode = v9fs_evict_inode,
.show_options = generic_show_options,
.umount_begin = v9fs_umount_begin,
.write_inode = v9fs_write_inode_dotl,
};
struct file_system_type v9fs_fs_type = {
.name = "9p",
.mount = v9fs_mount,
.kill_sb = v9fs_kill_super,
.owner = THIS_MODULE,
.fs_flags = FS_RENAME_DOES_D_MOVE,
};
MODULE_ALIAS_FS("9p");

View File

@@ -0,0 +1,195 @@
/*
* Copyright IBM Corporation, 2010
* Author Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2.1 of the GNU Lesser General Public License
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it would be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/uio.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
#include "fid.h"
#include "xattr.h"
ssize_t v9fs_fid_xattr_get(struct p9_fid *fid, const char *name,
void *buffer, size_t buffer_size)
{
ssize_t retval;
u64 attr_size;
struct p9_fid *attr_fid;
struct kvec kvec = {.iov_base = buffer, .iov_len = buffer_size};
struct iov_iter to;
int err;
iov_iter_kvec(&to, READ | ITER_KVEC, &kvec, 1, buffer_size);
attr_fid = p9_client_xattrwalk(fid, name, &attr_size);
if (IS_ERR(attr_fid)) {
retval = PTR_ERR(attr_fid);
p9_debug(P9_DEBUG_VFS, "p9_client_attrwalk failed %zd\n",
retval);
return retval;
}
if (attr_size > buffer_size) {
if (!buffer_size) /* request to get the attr_size */
retval = attr_size;
else
retval = -ERANGE;
} else {
iov_iter_truncate(&to, attr_size);
retval = p9_client_read(attr_fid, 0, &to, &err);
if (err)
retval = err;
}
p9_client_clunk(attr_fid);
return retval;
}
/*
* v9fs_xattr_get()
*
* Copy an extended attribute into the buffer
* provided, or compute the buffer size required.
* Buffer is NULL to compute the size of the buffer required.
*
* Returns a negative error number on failure, or the number of bytes
* used / required on success.
*/
ssize_t v9fs_xattr_get(struct dentry *dentry, const char *name,
void *buffer, size_t buffer_size)
{
struct p9_fid *fid;
p9_debug(P9_DEBUG_VFS, "name = %s value_len = %zu\n",
name, buffer_size);
fid = v9fs_fid_lookup(dentry);
if (IS_ERR(fid))
return PTR_ERR(fid);
return v9fs_fid_xattr_get(fid, name, buffer, buffer_size);
}
/*
* v9fs_xattr_set()
*
* Create, replace or remove an extended attribute for this inode. Buffer
* is NULL to remove an existing extended attribute, and non-NULL to
* either replace an existing extended attribute, or create a new extended
* attribute. The flags XATTR_REPLACE and XATTR_CREATE
* specify that an extended attribute must exist and must not exist
* previous to the call, respectively.
*
* Returns 0, or a negative error number on failure.
*/
int v9fs_xattr_set(struct dentry *dentry, const char *name,
const void *value, size_t value_len, int flags)
{
struct p9_fid *fid = v9fs_fid_lookup(dentry);
if (IS_ERR(fid))
return PTR_ERR(fid);
return v9fs_fid_xattr_set(fid, name, value, value_len, flags);
}
int v9fs_fid_xattr_set(struct p9_fid *fid, const char *name,
const void *value, size_t value_len, int flags)
{
struct kvec kvec = {.iov_base = (void *)value, .iov_len = value_len};
struct iov_iter from;
int retval, err;
iov_iter_kvec(&from, WRITE | ITER_KVEC, &kvec, 1, value_len);
p9_debug(P9_DEBUG_VFS, "name = %s value_len = %zu flags = %d\n",
name, value_len, flags);
/* Clone it */
fid = p9_client_walk(fid, 0, NULL, 1);
if (IS_ERR(fid))
return PTR_ERR(fid);
/*
* On success fid points to xattr
*/
retval = p9_client_xattrcreate(fid, name, value_len, flags);
if (retval < 0)
p9_debug(P9_DEBUG_VFS, "p9_client_xattrcreate failed %d\n",
retval);
else
p9_client_write(fid, 0, &from, &retval);
err = p9_client_clunk(fid);
if (!retval && err)
retval = err;
return retval;
}
ssize_t v9fs_listxattr(struct dentry *dentry, char *buffer, size_t buffer_size)
{
return v9fs_xattr_get(dentry, NULL, buffer, buffer_size);
}
static int v9fs_xattr_handler_get(const struct xattr_handler *handler,
struct dentry *dentry, const char *name,
void *buffer, size_t size)
{
const char *full_name = xattr_full_name(handler, name);
if (strcmp(name, "") == 0)
return -EINVAL;
return v9fs_xattr_get(dentry, full_name, buffer, size);
}
static int v9fs_xattr_handler_set(const struct xattr_handler *handler,
struct dentry *dentry, const char *name,
const void *value, size_t size, int flags)
{
const char *full_name = xattr_full_name(handler, name);
if (strcmp(name, "") == 0)
return -EINVAL;
return v9fs_xattr_set(dentry, full_name, value, size, flags);
}
static struct xattr_handler v9fs_xattr_user_handler = {
.prefix = XATTR_USER_PREFIX,
.get = v9fs_xattr_handler_get,
.set = v9fs_xattr_handler_set,
};
static struct xattr_handler v9fs_xattr_trusted_handler = {
.prefix = XATTR_TRUSTED_PREFIX,
.get = v9fs_xattr_handler_get,
.set = v9fs_xattr_handler_set,
};
#ifdef CONFIG_9P_FS_SECURITY
static struct xattr_handler v9fs_xattr_security_handler = {
.prefix = XATTR_SECURITY_PREFIX,
.get = v9fs_xattr_handler_get,
.set = v9fs_xattr_handler_set,
};
#endif
const struct xattr_handler *v9fs_xattr_handlers[] = {
&v9fs_xattr_user_handler,
&v9fs_xattr_trusted_handler,
#ifdef CONFIG_9P_FS_POSIX_ACL
&v9fs_xattr_acl_access_handler,
&v9fs_xattr_acl_default_handler,
#endif
#ifdef CONFIG_9P_FS_SECURITY
&v9fs_xattr_security_handler,
#endif
NULL
};

View File

@@ -0,0 +1,34 @@
/*
* Copyright IBM Corporation, 2010
* Author Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2.1 of the GNU Lesser General Public License
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it would be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
*/
#ifndef FS_9P_XATTR_H
#define FS_9P_XATTR_H
#include <linux/xattr.h>
#include <net/9p/9p.h>
#include <net/9p/client.h>
extern const struct xattr_handler *v9fs_xattr_handlers[];
extern const struct xattr_handler v9fs_xattr_acl_access_handler;
extern const struct xattr_handler v9fs_xattr_acl_default_handler;
extern ssize_t v9fs_fid_xattr_get(struct p9_fid *, const char *,
void *, size_t);
extern ssize_t v9fs_xattr_get(struct dentry *, const char *,
void *, size_t);
extern int v9fs_fid_xattr_set(struct p9_fid *, const char *,
const void *, size_t, int);
extern int v9fs_xattr_set(struct dentry *, const char *,
const void *, size_t, int);
extern ssize_t v9fs_listxattr(struct dentry *, char *, size_t);
#endif /* FS_9P_XATTR_H */