![Chuck Lever](/assets/img/avatar_default.png)
Down the road we want to eliminate the use of the global kernel lock entirely from the NFS client. To do this, we need to protect the fields in the nfs_inode structure adequately. Start by serializing updates to the "cache_validity" field. Note this change addresses an SMP hang found by njw@osdl.org, where processes deadlock because nfs_end_data_update and nfs_revalidate_mapping update the "cache_validity" field without proper serialization. Test plan: Millions of fsx ops on SMP clients. Run Nick Wilson's breaknfs program on large SMP clients. Signed-off-by: Chuck Lever <cel@netapp.com> Cc: Trond Myklebust <trond.myklebust@fys.uio.no> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
406 lines
9.3 KiB
C
406 lines
9.3 KiB
C
#include <linux/fs.h>
|
|
#include <linux/nfs.h>
|
|
#include <linux/nfs3.h>
|
|
#include <linux/nfs_fs.h>
|
|
#include <linux/posix_acl_xattr.h>
|
|
#include <linux/nfsacl.h>
|
|
|
|
#define NFSDBG_FACILITY NFSDBG_PROC
|
|
|
|
ssize_t nfs3_listxattr(struct dentry *dentry, char *buffer, size_t size)
|
|
{
|
|
struct inode *inode = dentry->d_inode;
|
|
struct posix_acl *acl;
|
|
int pos=0, len=0;
|
|
|
|
# define output(s) do { \
|
|
if (pos + sizeof(s) <= size) { \
|
|
memcpy(buffer + pos, s, sizeof(s)); \
|
|
pos += sizeof(s); \
|
|
} \
|
|
len += sizeof(s); \
|
|
} while(0)
|
|
|
|
acl = nfs3_proc_getacl(inode, ACL_TYPE_ACCESS);
|
|
if (IS_ERR(acl))
|
|
return PTR_ERR(acl);
|
|
if (acl) {
|
|
output("system.posix_acl_access");
|
|
posix_acl_release(acl);
|
|
}
|
|
|
|
if (S_ISDIR(inode->i_mode)) {
|
|
acl = nfs3_proc_getacl(inode, ACL_TYPE_DEFAULT);
|
|
if (IS_ERR(acl))
|
|
return PTR_ERR(acl);
|
|
if (acl) {
|
|
output("system.posix_acl_default");
|
|
posix_acl_release(acl);
|
|
}
|
|
}
|
|
|
|
# undef output
|
|
|
|
if (!buffer || len <= size)
|
|
return len;
|
|
return -ERANGE;
|
|
}
|
|
|
|
ssize_t nfs3_getxattr(struct dentry *dentry, const char *name,
|
|
void *buffer, size_t size)
|
|
{
|
|
struct inode *inode = dentry->d_inode;
|
|
struct posix_acl *acl;
|
|
int type, error = 0;
|
|
|
|
if (strcmp(name, POSIX_ACL_XATTR_ACCESS) == 0)
|
|
type = ACL_TYPE_ACCESS;
|
|
else if (strcmp(name, POSIX_ACL_XATTR_DEFAULT) == 0)
|
|
type = ACL_TYPE_DEFAULT;
|
|
else
|
|
return -EOPNOTSUPP;
|
|
|
|
acl = nfs3_proc_getacl(inode, type);
|
|
if (IS_ERR(acl))
|
|
return PTR_ERR(acl);
|
|
else if (acl) {
|
|
if (type == ACL_TYPE_ACCESS && acl->a_count == 0)
|
|
error = -ENODATA;
|
|
else
|
|
error = posix_acl_to_xattr(acl, buffer, size);
|
|
posix_acl_release(acl);
|
|
} else
|
|
error = -ENODATA;
|
|
|
|
return error;
|
|
}
|
|
|
|
int nfs3_setxattr(struct dentry *dentry, const char *name,
|
|
const void *value, size_t size, int flags)
|
|
{
|
|
struct inode *inode = dentry->d_inode;
|
|
struct posix_acl *acl;
|
|
int type, error;
|
|
|
|
if (strcmp(name, POSIX_ACL_XATTR_ACCESS) == 0)
|
|
type = ACL_TYPE_ACCESS;
|
|
else if (strcmp(name, POSIX_ACL_XATTR_DEFAULT) == 0)
|
|
type = ACL_TYPE_DEFAULT;
|
|
else
|
|
return -EOPNOTSUPP;
|
|
|
|
acl = posix_acl_from_xattr(value, size);
|
|
if (IS_ERR(acl))
|
|
return PTR_ERR(acl);
|
|
error = nfs3_proc_setacl(inode, type, acl);
|
|
posix_acl_release(acl);
|
|
|
|
return error;
|
|
}
|
|
|
|
int nfs3_removexattr(struct dentry *dentry, const char *name)
|
|
{
|
|
struct inode *inode = dentry->d_inode;
|
|
int type;
|
|
|
|
if (strcmp(name, POSIX_ACL_XATTR_ACCESS) == 0)
|
|
type = ACL_TYPE_ACCESS;
|
|
else if (strcmp(name, POSIX_ACL_XATTR_DEFAULT) == 0)
|
|
type = ACL_TYPE_DEFAULT;
|
|
else
|
|
return -EOPNOTSUPP;
|
|
|
|
return nfs3_proc_setacl(inode, type, NULL);
|
|
}
|
|
|
|
static void __nfs3_forget_cached_acls(struct nfs_inode *nfsi)
|
|
{
|
|
if (!IS_ERR(nfsi->acl_access)) {
|
|
posix_acl_release(nfsi->acl_access);
|
|
nfsi->acl_access = ERR_PTR(-EAGAIN);
|
|
}
|
|
if (!IS_ERR(nfsi->acl_default)) {
|
|
posix_acl_release(nfsi->acl_default);
|
|
nfsi->acl_default = ERR_PTR(-EAGAIN);
|
|
}
|
|
}
|
|
|
|
void nfs3_forget_cached_acls(struct inode *inode)
|
|
{
|
|
dprintk("NFS: nfs3_forget_cached_acls(%s/%ld)\n", inode->i_sb->s_id,
|
|
inode->i_ino);
|
|
spin_lock(&inode->i_lock);
|
|
__nfs3_forget_cached_acls(NFS_I(inode));
|
|
spin_unlock(&inode->i_lock);
|
|
}
|
|
|
|
static struct posix_acl *nfs3_get_cached_acl(struct inode *inode, int type)
|
|
{
|
|
struct nfs_inode *nfsi = NFS_I(inode);
|
|
struct posix_acl *acl = ERR_PTR(-EINVAL);
|
|
|
|
spin_lock(&inode->i_lock);
|
|
switch(type) {
|
|
case ACL_TYPE_ACCESS:
|
|
acl = nfsi->acl_access;
|
|
break;
|
|
|
|
case ACL_TYPE_DEFAULT:
|
|
acl = nfsi->acl_default;
|
|
break;
|
|
|
|
default:
|
|
goto out;
|
|
}
|
|
if (IS_ERR(acl))
|
|
acl = ERR_PTR(-EAGAIN);
|
|
else
|
|
acl = posix_acl_dup(acl);
|
|
out:
|
|
spin_unlock(&inode->i_lock);
|
|
dprintk("NFS: nfs3_get_cached_acl(%s/%ld, %d) = %p\n", inode->i_sb->s_id,
|
|
inode->i_ino, type, acl);
|
|
return acl;
|
|
}
|
|
|
|
static void nfs3_cache_acls(struct inode *inode, struct posix_acl *acl,
|
|
struct posix_acl *dfacl)
|
|
{
|
|
struct nfs_inode *nfsi = NFS_I(inode);
|
|
|
|
dprintk("nfs3_cache_acls(%s/%ld, %p, %p)\n", inode->i_sb->s_id,
|
|
inode->i_ino, acl, dfacl);
|
|
spin_lock(&inode->i_lock);
|
|
__nfs3_forget_cached_acls(NFS_I(inode));
|
|
nfsi->acl_access = posix_acl_dup(acl);
|
|
nfsi->acl_default = posix_acl_dup(dfacl);
|
|
spin_unlock(&inode->i_lock);
|
|
}
|
|
|
|
struct posix_acl *nfs3_proc_getacl(struct inode *inode, int type)
|
|
{
|
|
struct nfs_server *server = NFS_SERVER(inode);
|
|
struct nfs_fattr fattr;
|
|
struct page *pages[NFSACL_MAXPAGES] = { };
|
|
struct nfs3_getaclargs args = {
|
|
.fh = NFS_FH(inode),
|
|
/* The xdr layer may allocate pages here. */
|
|
.pages = pages,
|
|
};
|
|
struct nfs3_getaclres res = {
|
|
.fattr = &fattr,
|
|
};
|
|
struct posix_acl *acl;
|
|
int status, count;
|
|
|
|
if (!nfs_server_capable(inode, NFS_CAP_ACLS))
|
|
return ERR_PTR(-EOPNOTSUPP);
|
|
|
|
status = nfs_revalidate_inode(server, inode);
|
|
if (status < 0)
|
|
return ERR_PTR(status);
|
|
acl = nfs3_get_cached_acl(inode, type);
|
|
if (acl != ERR_PTR(-EAGAIN))
|
|
return acl;
|
|
acl = NULL;
|
|
|
|
/*
|
|
* Only get the access acl when explicitly requested: We don't
|
|
* need it for access decisions, and only some applications use
|
|
* it. Applications which request the access acl first are not
|
|
* penalized from this optimization.
|
|
*/
|
|
if (type == ACL_TYPE_ACCESS)
|
|
args.mask |= NFS_ACLCNT|NFS_ACL;
|
|
if (S_ISDIR(inode->i_mode))
|
|
args.mask |= NFS_DFACLCNT|NFS_DFACL;
|
|
if (args.mask == 0)
|
|
return NULL;
|
|
|
|
dprintk("NFS call getacl\n");
|
|
status = rpc_call(server->client_acl, ACLPROC3_GETACL,
|
|
&args, &res, 0);
|
|
dprintk("NFS reply getacl: %d\n", status);
|
|
|
|
/* pages may have been allocated at the xdr layer. */
|
|
for (count = 0; count < NFSACL_MAXPAGES && args.pages[count]; count++)
|
|
__free_page(args.pages[count]);
|
|
|
|
switch (status) {
|
|
case 0:
|
|
status = nfs_refresh_inode(inode, &fattr);
|
|
break;
|
|
case -EPFNOSUPPORT:
|
|
case -EPROTONOSUPPORT:
|
|
dprintk("NFS_V3_ACL extension not supported; disabling\n");
|
|
server->caps &= ~NFS_CAP_ACLS;
|
|
case -ENOTSUPP:
|
|
status = -EOPNOTSUPP;
|
|
default:
|
|
goto getout;
|
|
}
|
|
if ((args.mask & res.mask) != args.mask) {
|
|
status = -EIO;
|
|
goto getout;
|
|
}
|
|
|
|
if (res.acl_access != NULL) {
|
|
if (posix_acl_equiv_mode(res.acl_access, NULL) == 0) {
|
|
posix_acl_release(res.acl_access);
|
|
res.acl_access = NULL;
|
|
}
|
|
}
|
|
nfs3_cache_acls(inode, res.acl_access, res.acl_default);
|
|
|
|
switch(type) {
|
|
case ACL_TYPE_ACCESS:
|
|
acl = res.acl_access;
|
|
res.acl_access = NULL;
|
|
break;
|
|
|
|
case ACL_TYPE_DEFAULT:
|
|
acl = res.acl_default;
|
|
res.acl_default = NULL;
|
|
}
|
|
|
|
getout:
|
|
posix_acl_release(res.acl_access);
|
|
posix_acl_release(res.acl_default);
|
|
|
|
if (status != 0) {
|
|
posix_acl_release(acl);
|
|
acl = ERR_PTR(status);
|
|
}
|
|
return acl;
|
|
}
|
|
|
|
static int nfs3_proc_setacls(struct inode *inode, struct posix_acl *acl,
|
|
struct posix_acl *dfacl)
|
|
{
|
|
struct nfs_server *server = NFS_SERVER(inode);
|
|
struct nfs_fattr fattr;
|
|
struct page *pages[NFSACL_MAXPAGES] = { };
|
|
struct nfs3_setaclargs args = {
|
|
.inode = inode,
|
|
.mask = NFS_ACL,
|
|
.acl_access = acl,
|
|
.pages = pages,
|
|
};
|
|
int status, count;
|
|
|
|
status = -EOPNOTSUPP;
|
|
if (!nfs_server_capable(inode, NFS_CAP_ACLS))
|
|
goto out;
|
|
|
|
/* We are doing this here, because XDR marshalling can only
|
|
return -ENOMEM. */
|
|
status = -ENOSPC;
|
|
if (acl != NULL && acl->a_count > NFS_ACL_MAX_ENTRIES)
|
|
goto out;
|
|
if (dfacl != NULL && dfacl->a_count > NFS_ACL_MAX_ENTRIES)
|
|
goto out;
|
|
if (S_ISDIR(inode->i_mode)) {
|
|
args.mask |= NFS_DFACL;
|
|
args.acl_default = dfacl;
|
|
}
|
|
|
|
dprintk("NFS call setacl\n");
|
|
nfs_begin_data_update(inode);
|
|
status = rpc_call(server->client_acl, ACLPROC3_SETACL,
|
|
&args, &fattr, 0);
|
|
spin_lock(&inode->i_lock);
|
|
NFS_I(inode)->cache_validity |= NFS_INO_INVALID_ACCESS;
|
|
spin_unlock(&inode->i_lock);
|
|
nfs_end_data_update(inode);
|
|
dprintk("NFS reply setacl: %d\n", status);
|
|
|
|
/* pages may have been allocated at the xdr layer. */
|
|
for (count = 0; count < NFSACL_MAXPAGES && args.pages[count]; count++)
|
|
__free_page(args.pages[count]);
|
|
|
|
switch (status) {
|
|
case 0:
|
|
status = nfs_refresh_inode(inode, &fattr);
|
|
break;
|
|
case -EPFNOSUPPORT:
|
|
case -EPROTONOSUPPORT:
|
|
dprintk("NFS_V3_ACL SETACL RPC not supported"
|
|
"(will not retry)\n");
|
|
server->caps &= ~NFS_CAP_ACLS;
|
|
case -ENOTSUPP:
|
|
status = -EOPNOTSUPP;
|
|
}
|
|
out:
|
|
return status;
|
|
}
|
|
|
|
int nfs3_proc_setacl(struct inode *inode, int type, struct posix_acl *acl)
|
|
{
|
|
struct posix_acl *alloc = NULL, *dfacl = NULL;
|
|
int status;
|
|
|
|
if (S_ISDIR(inode->i_mode)) {
|
|
switch(type) {
|
|
case ACL_TYPE_ACCESS:
|
|
alloc = dfacl = nfs3_proc_getacl(inode,
|
|
ACL_TYPE_DEFAULT);
|
|
if (IS_ERR(alloc))
|
|
goto fail;
|
|
break;
|
|
|
|
case ACL_TYPE_DEFAULT:
|
|
dfacl = acl;
|
|
alloc = acl = nfs3_proc_getacl(inode,
|
|
ACL_TYPE_ACCESS);
|
|
if (IS_ERR(alloc))
|
|
goto fail;
|
|
break;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
} else if (type != ACL_TYPE_ACCESS)
|
|
return -EINVAL;
|
|
|
|
if (acl == NULL) {
|
|
alloc = acl = posix_acl_from_mode(inode->i_mode, GFP_KERNEL);
|
|
if (IS_ERR(alloc))
|
|
goto fail;
|
|
}
|
|
status = nfs3_proc_setacls(inode, acl, dfacl);
|
|
posix_acl_release(alloc);
|
|
return status;
|
|
|
|
fail:
|
|
return PTR_ERR(alloc);
|
|
}
|
|
|
|
int nfs3_proc_set_default_acl(struct inode *dir, struct inode *inode,
|
|
mode_t mode)
|
|
{
|
|
struct posix_acl *dfacl, *acl;
|
|
int error = 0;
|
|
|
|
dfacl = nfs3_proc_getacl(dir, ACL_TYPE_DEFAULT);
|
|
if (IS_ERR(dfacl)) {
|
|
error = PTR_ERR(dfacl);
|
|
return (error == -EOPNOTSUPP) ? 0 : error;
|
|
}
|
|
if (!dfacl)
|
|
return 0;
|
|
acl = posix_acl_clone(dfacl, GFP_KERNEL);
|
|
error = -ENOMEM;
|
|
if (!acl)
|
|
goto out_release_dfacl;
|
|
error = posix_acl_create_masq(acl, &mode);
|
|
if (error < 0)
|
|
goto out_release_acl;
|
|
error = nfs3_proc_setacls(inode, acl, S_ISDIR(inode->i_mode) ?
|
|
dfacl : NULL);
|
|
out_release_acl:
|
|
posix_acl_release(acl);
|
|
out_release_dfacl:
|
|
posix_acl_release(dfacl);
|
|
return error;
|
|
}
|