diff options
Diffstat (limited to 'fs/posix_acl.c')
| -rw-r--r-- | fs/posix_acl.c | 122 | 
1 files changed, 78 insertions, 44 deletions
| diff --git a/fs/posix_acl.c b/fs/posix_acl.c index 711dd5170376..8a4a266beff3 100644 --- a/fs/posix_acl.c +++ b/fs/posix_acl.c @@ -21,7 +21,7 @@  #include <linux/export.h>  #include <linux/user_namespace.h> -struct posix_acl **acl_by_type(struct inode *inode, int type) +static struct posix_acl **acl_by_type(struct inode *inode, int type)  {  	switch (type) {  	case ACL_TYPE_ACCESS: @@ -32,19 +32,22 @@ struct posix_acl **acl_by_type(struct inode *inode, int type)  		BUG();  	}  } -EXPORT_SYMBOL(acl_by_type);  struct posix_acl *get_cached_acl(struct inode *inode, int type)  {  	struct posix_acl **p = acl_by_type(inode, type); -	struct posix_acl *acl = ACCESS_ONCE(*p); -	if (acl) { -		spin_lock(&inode->i_lock); -		acl = *p; -		if (acl != ACL_NOT_CACHED) -			acl = posix_acl_dup(acl); -		spin_unlock(&inode->i_lock); +	struct posix_acl *acl; + +	for (;;) { +		rcu_read_lock(); +		acl = rcu_dereference(*p); +		if (!acl || is_uncached_acl(acl) || +		    atomic_inc_not_zero(&acl->a_refcount)) +			break; +		rcu_read_unlock(); +		cpu_relax();  	} +	rcu_read_unlock();  	return acl;  }  EXPORT_SYMBOL(get_cached_acl); @@ -59,58 +62,72 @@ void set_cached_acl(struct inode *inode, int type, struct posix_acl *acl)  {  	struct posix_acl **p = acl_by_type(inode, type);  	struct posix_acl *old; -	spin_lock(&inode->i_lock); -	old = *p; -	rcu_assign_pointer(*p, posix_acl_dup(acl)); -	spin_unlock(&inode->i_lock); -	if (old != ACL_NOT_CACHED) + +	old = xchg(p, posix_acl_dup(acl)); +	if (!is_uncached_acl(old))  		posix_acl_release(old);  }  EXPORT_SYMBOL(set_cached_acl); -void forget_cached_acl(struct inode *inode, int type) +static void __forget_cached_acl(struct posix_acl **p)  { -	struct posix_acl **p = acl_by_type(inode, type);  	struct posix_acl *old; -	spin_lock(&inode->i_lock); -	old = *p; -	*p = ACL_NOT_CACHED; -	spin_unlock(&inode->i_lock); -	if (old != ACL_NOT_CACHED) + +	old = xchg(p, ACL_NOT_CACHED); +	if (!is_uncached_acl(old))  		posix_acl_release(old);  } + +void forget_cached_acl(struct inode *inode, int type) +{ +	__forget_cached_acl(acl_by_type(inode, type)); +}  EXPORT_SYMBOL(forget_cached_acl);  void forget_all_cached_acls(struct inode *inode)  { -	struct posix_acl *old_access, *old_default; -	spin_lock(&inode->i_lock); -	old_access = inode->i_acl; -	old_default = inode->i_default_acl; -	inode->i_acl = inode->i_default_acl = ACL_NOT_CACHED; -	spin_unlock(&inode->i_lock); -	if (old_access != ACL_NOT_CACHED) -		posix_acl_release(old_access); -	if (old_default != ACL_NOT_CACHED) -		posix_acl_release(old_default); +	__forget_cached_acl(&inode->i_acl); +	__forget_cached_acl(&inode->i_default_acl);  }  EXPORT_SYMBOL(forget_all_cached_acls);  struct posix_acl *get_acl(struct inode *inode, int type)  { +	void *sentinel; +	struct posix_acl **p;  	struct posix_acl *acl; +	/* +	 * The sentinel is used to detect when another operation like +	 * set_cached_acl() or forget_cached_acl() races with get_acl(). +	 * It is guaranteed that is_uncached_acl(sentinel) is true. +	 */ +  	acl = get_cached_acl(inode, type); -	if (acl != ACL_NOT_CACHED) +	if (!is_uncached_acl(acl))  		return acl;  	if (!IS_POSIXACL(inode))  		return NULL; +	sentinel = uncached_acl_sentinel(current); +	p = acl_by_type(inode, type); +  	/* -	 * A filesystem can force a ACL callback by just never filling the -	 * ACL cache. But normally you'd fill the cache either at inode -	 * instantiation time, or on the first ->get_acl call. +	 * If the ACL isn't being read yet, set our sentinel.  Otherwise, the +	 * current value of the ACL will not be ACL_NOT_CACHED and so our own +	 * sentinel will not be set; another task will update the cache.  We +	 * could wait for that other task to complete its job, but it's easier +	 * to just call ->get_acl to fetch the ACL ourself.  (This is going to +	 * be an unlikely race.) +	 */ +	if (cmpxchg(p, ACL_NOT_CACHED, sentinel) != ACL_NOT_CACHED) +		/* fall through */ ; + +	/* +	 * Normally, the ACL returned by ->get_acl will be cached. +	 * A filesystem can prevent that by calling +	 * forget_cached_acl(inode, type) in ->get_acl.  	 *  	 * If the filesystem doesn't have a get_acl() function at all, we'll  	 * just create the negative cache entry. @@ -119,7 +136,24 @@ struct posix_acl *get_acl(struct inode *inode, int type)  		set_cached_acl(inode, type, NULL);  		return NULL;  	} -	return inode->i_op->get_acl(inode, type); +	acl = inode->i_op->get_acl(inode, type); + +	if (IS_ERR(acl)) { +		/* +		 * Remove our sentinel so that we don't block future attempts +		 * to cache the ACL. +		 */ +		cmpxchg(p, sentinel, ACL_NOT_CACHED); +		return acl; +	} + +	/* +	 * Cache the result, but only if our sentinel is still in place. +	 */ +	posix_acl_dup(acl); +	if (unlikely(cmpxchg(p, sentinel, acl) != sentinel)) +		posix_acl_release(acl); +	return acl;  }  EXPORT_SYMBOL(get_acl); @@ -763,18 +797,18 @@ EXPORT_SYMBOL (posix_acl_to_xattr);  static int  posix_acl_xattr_get(const struct xattr_handler *handler, -		    struct dentry *dentry, const char *name, -		    void *value, size_t size) +		    struct dentry *unused, struct inode *inode, +		    const char *name, void *value, size_t size)  {  	struct posix_acl *acl;  	int error; -	if (!IS_POSIXACL(d_backing_inode(dentry))) +	if (!IS_POSIXACL(inode))  		return -EOPNOTSUPP; -	if (d_is_symlink(dentry)) +	if (S_ISLNK(inode->i_mode))  		return -EOPNOTSUPP; -	acl = get_acl(d_backing_inode(dentry), handler->flags); +	acl = get_acl(inode, handler->flags);  	if (IS_ERR(acl))  		return PTR_ERR(acl);  	if (acl == NULL) @@ -788,10 +822,10 @@ posix_acl_xattr_get(const struct xattr_handler *handler,  static int  posix_acl_xattr_set(const struct xattr_handler *handler, -		    struct dentry *dentry, const char *name, -		    const void *value, size_t size, int flags) +		    struct dentry *unused, struct inode *inode, +		    const char *name, const void *value, +		    size_t size, int flags)  { -	struct inode *inode = d_backing_inode(dentry);  	struct posix_acl *acl = NULL;  	int ret; | 
