Release 4.7 fs/namei.c
/*
* linux/fs/namei.c
*
* Copyright (C) 1991, 1992 Linus Torvalds
*/
/*
* Some corrections by tytso.
*/
/* [Feb 1997 T. Schoebel-Theuer] Complete rewrite of the pathname
* lookup logic.
*/
/* [Feb-Apr 2000, AV] Rewrite to the new namespace architecture.
*/
#include <linux/init.h>
#include <linux/export.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/namei.h>
#include <linux/pagemap.h>
#include <linux/fsnotify.h>
#include <linux/personality.h>
#include <linux/security.h>
#include <linux/ima.h>
#include <linux/syscalls.h>
#include <linux/mount.h>
#include <linux/audit.h>
#include <linux/capability.h>
#include <linux/file.h>
#include <linux/fcntl.h>
#include <linux/device_cgroup.h>
#include <linux/fs_struct.h>
#include <linux/posix_acl.h>
#include <linux/hash.h>
#include <linux/bitops.h>
#include <asm/uaccess.h>
#include "internal.h"
#include "mount.h"
/* [Feb-1997 T. Schoebel-Theuer]
* Fundamental changes in the pathname lookup mechanisms (namei)
* were necessary because of omirr. The reason is that omirr needs
* to know the _real_ pathname, not the user-supplied one, in case
* of symlinks (and also when transname replacements occur).
*
* The new code replaces the old recursive symlink resolution with
* an iterative one (in case of non-nested symlink chains). It does
* this with calls to <fs>_follow_link().
* As a side effect, dir_namei(), _namei() and follow_link() are now
* replaced with a single function lookup_dentry() that can handle all
* the special cases of the former code.
*
* With the new dcache, the pathname is stored at each inode, at least as
* long as the refcount of the inode is positive. As a side effect, the
* size of the dcache depends on the inode cache and thus is dynamic.
*
* [29-Apr-1998 C. Scott Ananian] Updated above description of symlink
* resolution to correspond with current state of the code.
*
* Note that the symlink resolution is not *completely* iterative.
* There is still a significant amount of tail- and mid- recursion in
* the algorithm. Also, note that <fs>_readlink() is not used in
* lookup_dentry(): lookup_dentry() on the result of <fs>_readlink()
* may return different results than <fs>_follow_link(). Many virtual
* filesystems (including /proc) exhibit this behavior.
*/
/* [24-Feb-97 T. Schoebel-Theuer] Side effects caused by new implementation:
* New symlink semantics: when open() is called with flags O_CREAT | O_EXCL
* and the name already exists in form of a symlink, try to create the new
* name indicated by the symlink. The old code always complained that the
* name already exists, due to not following the symlink even if its target
* is nonexistent. The new semantics affects also mknod() and link() when
* the name is a symlink pointing to a non-existent name.
*
* I don't know which semantics is the right one, since I have no access
* to standards. But I found by trial that HP-UX 9.0 has the full "new"
* semantics implemented, while SunOS 4.1.1 and Solaris (SunOS 5.4) have the
* "old" one. Personally, I think the new semantics is much more logical.
* Note that "ln old new" where "new" is a symlink pointing to a non-existing
* file does succeed in both HP-UX and SunOs, but not in Solaris
* and in the old Linux semantics.
*/
/* [16-Dec-97 Kevin Buhr] For security reasons, we change some symlink
* semantics. See the comments in "open_namei" and "do_link" below.
*
* [10-Sep-98 Alan Modra] Another symlink change.
*/
/* [Feb-Apr 2000 AV] Complete rewrite. Rules for symlinks:
* inside the path - always follow.
* in the last component in creation/removal/renaming - never follow.
* if LOOKUP_FOLLOW passed - follow.
* if the pathname has trailing slashes - follow.
* otherwise - don't follow.
* (applied in that order).
*
* [Jun 2000 AV] Inconsistent behaviour of open() in case if flags==O_CREAT
* restored for 2.4. This is the last surviving part of old 4.2BSD bug.
* During the 2.4 we need to fix the userland stuff depending on it -
* hopefully we will be able to get rid of that wart in 2.5. So far only
* XEmacs seems to be relying on it...
*/
/*
* [Sep 2001 AV] Single-semaphore locking scheme (kudos to David Holland)
* implemented. Let's see if raised priority of ->s_vfs_rename_mutex gives
* any extra contention...
*/
/* In order to reduce some races, while at the same time doing additional
* checking and hopefully speeding things up, we copy filenames to the
* kernel data space before using them..
*
* POSIX.1 2.4: an empty pathname is invalid (ENOENT).
* PATH_MAX includes the nul terminator --RR.
*/
#define EMBEDDED_NAME_MAX (PATH_MAX - offsetof(struct filename, iname))
struct filename *
getname_flags(const char __user *filename, int flags, int *empty)
{
struct filename *result;
char *kname;
int len;
result = audit_reusename(filename);
if (result)
return result;
result = __getname();
if (unlikely(!result))
return ERR_PTR(-ENOMEM);
/*
* First, try to embed the struct filename inside the names_cache
* allocation
*/
kname = (char *)result->iname;
result->name = kname;
len = strncpy_from_user(kname, filename, EMBEDDED_NAME_MAX);
if (unlikely(len < 0)) {
__putname(result);
return ERR_PTR(len);
}
/*
* Uh-oh. We have a name that's approaching PATH_MAX. Allocate a
* separate struct filename so we can dedicate the entire
* names_cache allocation for the pathname, and re-do the copy from
* userland.
*/
if (unlikely(len == EMBEDDED_NAME_MAX)) {
const size_t size = offsetof(struct filename, iname[1]);
kname = (char *)result;
/*
* size is chosen that way we to guarantee that
* result->iname[0] is within the same object and that
* kname can't be equal to result->iname, no matter what.
*/
result = kzalloc(size, GFP_KERNEL);
if (unlikely(!result)) {
__putname(kname);
return ERR_PTR(-ENOMEM);
}
result->name = kname;
len = strncpy_from_user(kname, filename, PATH_MAX);
if (unlikely(len < 0)) {
__putname(kname);
kfree(result);
return ERR_PTR(len);
}
if (unlikely(len == PATH_MAX)) {
__putname(kname);
kfree(result);
return ERR_PTR(-ENAMETOOLONG);
}
}
result->refcnt = 1;
/* The empty path is special. */
if (unlikely(!len)) {
if (empty)
*empty = 1;
if (!(flags & LOOKUP_EMPTY)) {
putname(result);
return ERR_PTR(-ENOENT);
}
}
result->uptr = filename;
result->aname = NULL;
audit_getname(result);
return result;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
al viro | al viro | 133 | 41.05% | 3 | 17.65% |
jeff layton | jeff layton | 103 | 31.79% | 3 | 17.65% |
linus torvalds | linus torvalds | 33 | 10.19% | 3 | 17.65% |
pre-git | pre-git | 26 | 8.02% | 4 | 23.53% |
eric paris | eric paris | 11 | 3.40% | 1 | 5.88% |
andy whitcroft | andy whitcroft | 11 | 3.40% | 1 | 5.88% |
paul moore | paul moore | 6 | 1.85% | 1 | 5.88% |
chris wright | chris wright | 1 | 0.31% | 1 | 5.88% |
| Total | 324 | 100.00% | 17 | 100.00% |
struct filename *
getname(const char __user * filename)
{
return getname_flags(filename, 0, NULL);
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
al viro | al viro | 19 | 82.61% | 1 | 25.00% |
jeff layton | jeff layton | 2 | 8.70% | 1 | 25.00% |
linus torvalds | linus torvalds | 1 | 4.35% | 1 | 25.00% |
andy whitcroft | andy whitcroft | 1 | 4.35% | 1 | 25.00% |
| Total | 23 | 100.00% | 4 | 100.00% |
struct filename *
getname_kernel(const char * filename)
{
struct filename *result;
int len = strlen(filename) + 1;
result = __getname();
if (unlikely(!result))
return ERR_PTR(-ENOMEM);
if (len <= EMBEDDED_NAME_MAX) {
result->name = (char *)result->iname;
} else if (len <= PATH_MAX) {
struct filename *tmp;
tmp = kmalloc(sizeof(*tmp), GFP_KERNEL);
if (unlikely(!tmp)) {
__putname(result);
return ERR_PTR(-ENOMEM);
}
tmp->name = (char *)result;
result = tmp;
} else {
__putname(result);
return ERR_PTR(-ENAMETOOLONG);
}
memcpy((char *)result->name, filename, len);
result->uptr = NULL;
result->aname = NULL;
result->refcnt = 1;
audit_getname(result);
return result;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
paul moore | paul moore | 118 | 63.44% | 3 | 60.00% |
linus torvalds | linus torvalds | 67 | 36.02% | 1 | 20.00% |
al viro | al viro | 1 | 0.54% | 1 | 20.00% |
| Total | 186 | 100.00% | 5 | 100.00% |
void putname(struct filename *name)
{
BUG_ON(name->refcnt <= 0);
if (--name->refcnt > 0)
return;
if (name->name != name->iname) {
__putname(name->name);
kfree(name);
} else
__putname(name);
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
paul moore | paul moore | 33 | 55.93% | 1 | 16.67% |
andrew morton | andrew morton | 9 | 15.25% | 1 | 16.67% |
chris wright | chris wright | 9 | 15.25% | 1 | 16.67% |
al viro | al viro | 5 | 8.47% | 1 | 16.67% |
jeff layton | jeff layton | 2 | 3.39% | 1 | 16.67% |
pre-git | pre-git | 1 | 1.69% | 1 | 16.67% |
| Total | 59 | 100.00% | 6 | 100.00% |
static int check_acl(struct inode *inode, int mask)
{
#ifdef CONFIG_FS_POSIX_ACL
struct posix_acl *acl;
if (mask & MAY_NOT_BLOCK) {
acl = get_cached_acl_rcu(inode, ACL_TYPE_ACCESS);
if (!acl)
return -EAGAIN;
/* no ->get_acl() calls in RCU mode... */
if (is_uncached_acl(acl))
return -ECHILD;
return posix_acl_permission(inode, acl, mask & ~MAY_NOT_BLOCK);
}
acl = get_acl(inode, ACL_TYPE_ACCESS);
if (IS_ERR(acl))
return PTR_ERR(acl);
if (acl) {
int error = posix_acl_permission(inode, acl, mask);
posix_acl_release(acl);
return error;
}
#endif
return -EAGAIN;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
linus torvalds | linus torvalds | 71 | 55.91% | 3 | 27.27% |
al viro | al viro | 25 | 19.69% | 2 | 18.18% |
christoph hellwig | christoph hellwig | 16 | 12.60% | 2 | 18.18% |
pre-git | pre-git | 9 | 7.09% | 2 | 18.18% |
ari savolainen | ari savolainen | 3 | 2.36% | 1 | 9.09% |
andreas gruenbacher | andreas gruenbacher | 3 | 2.36% | 1 | 9.09% |
| Total | 127 | 100.00% | 11 | 100.00% |
/*
* This does the basic permission checking
*/
static int acl_permission_check(struct inode *inode, int mask)
{
unsigned int mode = inode->i_mode;
if (likely(uid_eq(current_fsuid(), inode->i_uid)))
mode >>= 6;
else {
if (IS_POSIXACL(inode) && (mode & S_IRWXG)) {
int error = check_acl(inode, mask);
if (error != -EAGAIN)
return error;
}
if (in_group_p(inode->i_gid))
mode >>= 3;
}
/*
* If the DACs are ok we don't need any capability check.
*/
if ((mask & ~mode & (MAY_READ | MAY_WRITE | MAY_EXEC)) == 0)
return 0;
return -EACCES;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
christoph hellwig | christoph hellwig | 48 | 40.34% | 1 | 7.14% |
pre-git | pre-git | 34 | 28.57% | 3 | 21.43% |
linus torvalds | linus torvalds | 19 | 15.97% | 5 | 35.71% |
al viro | al viro | 11 | 9.24% | 2 | 14.29% |
eric w. biederman | eric w. biederman | 4 | 3.36% | 1 | 7.14% |
david howells | david howells | 2 | 1.68% | 1 | 7.14% |
nick piggin | nick piggin | 1 | 0.84% | 1 | 7.14% |
| Total | 119 | 100.00% | 14 | 100.00% |
/**
* generic_permission - check for access rights on a Posix-like filesystem
* @inode: inode to check access rights for
* @mask: right to check for (%MAY_READ, %MAY_WRITE, %MAY_EXEC, ...)
*
* Used to check for read/write/execute permissions on a file.
* We use "fsuid" for this, letting us set arbitrary permissions
* for filesystem access without changing the "normal" uids which
* are used for other things.
*
* generic_permission is rcu-walk aware. It returns -ECHILD in case an rcu-walk
* request cannot be satisfied (eg. requires blocking or too much complexity).
* It would then be called again in ref-walk mode.
*/
int generic_permission(struct inode *inode, int mask)
{
int ret;
/*
* Do the basic permission checks.
*/
ret = acl_permission_check(inode, mask);
if (ret != -EACCES)
return ret;
if (S_ISDIR(inode->i_mode)) {
/* DACs are overridable for directories */
if (capable_wrt_inode_uidgid(inode, CAP_DAC_OVERRIDE))
return 0;
if (!(mask & MAY_WRITE))
if (capable_wrt_inode_uidgid(inode,
CAP_DAC_READ_SEARCH))
return 0;
return -EACCES;
}
/*
* Read/write DACs are always overridable.
* Executable DACs are overridable when there is
* at least one exec bit set.
*/
if (!(mask & MAY_EXEC) || (inode->i_mode & S_IXUGO))
if (capable_wrt_inode_uidgid(inode, CAP_DAC_OVERRIDE))
return 0;
/*
* Searching includes executable on directories, else just read.
*/
mask &= MAY_READ | MAY_WRITE | MAY_EXEC;
if (mask == MAY_READ)
if (capable_wrt_inode_uidgid(inode, CAP_DAC_READ_SEARCH))
return 0;
return -EACCES;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
al viro | al viro | 55 | 37.67% | 1 | 8.33% |
linus torvalds | linus torvalds | 45 | 30.82% | 2 | 16.67% |
pre-git | pre-git | 25 | 17.12% | 4 | 33.33% |
serge hallyn | serge hallyn | 13 | 8.90% | 2 | 16.67% |
andy lutomirski | andy lutomirski | 4 | 2.74% | 1 | 8.33% |
andrew morton | andrew morton | 3 | 2.05% | 1 | 8.33% |
andreas gruenbacher | andreas gruenbacher | 1 | 0.68% | 1 | 8.33% |
| Total | 146 | 100.00% | 12 | 100.00% |
EXPORT_SYMBOL(generic_permission);
/*
* We _really_ want to just do "generic_permission()" without
* even looking at the inode->i_op values. So we keep a cache
* flag in inode->i_opflags, that says "this has not special
* permission function, use the fast case".
*/
static inline int do_inode_permission(struct inode *inode, int mask)
{
if (unlikely(!(inode->i_opflags & IOP_FASTPERM))) {
if (likely(inode->i_op->permission))
return inode->i_op->permission(inode, mask);
/* This gets set once for the inode lifetime */
spin_lock(&inode->i_lock);
inode->i_opflags |= IOP_FASTPERM;
spin_unlock(&inode->i_lock);
}
return generic_permission(inode, mask);
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
linus torvalds | linus torvalds | 85 | 100.00% | 1 | 100.00% |
| Total | 85 | 100.00% | 1 | 100.00% |
/**
* __inode_permission - Check for access rights to a given inode
* @inode: Inode to check permission on
* @mask: Right to check for (%MAY_READ, %MAY_WRITE, %MAY_EXEC)
*
* Check for read/write/execute permissions on an inode.
*
* When checking for MAY_APPEND, MAY_WRITE must also be set in @mask.
*
* This does not check for a read-only file system. You probably want
* inode_permission().
*/
int __inode_permission(struct inode *inode, int mask)
{
int retval;
if (unlikely(mask & MAY_WRITE)) {
/*
* Nobody gets write access to an immutable file.
*/
if (IS_IMMUTABLE(inode))
return -EACCES;
}
retval = do_inode_permission(inode, mask);
if (retval)
return retval;
retval = devcgroup_inode_permission(inode, mask);
if (retval)
return retval;
return security_inode_permission(inode, mask);
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
david howells | david howells | 55 | 69.62% | 1 | 20.00% |
pre-git | pre-git | 11 | 13.92% | 1 | 20.00% |
christoph hellwig | christoph hellwig | 7 | 8.86% | 1 | 20.00% |
stephen d. smalley | stephen d. smalley | 3 | 3.80% | 1 | 20.00% |
linus torvalds | linus torvalds | 3 | 3.80% | 1 | 20.00% |
| Total | 79 | 100.00% | 5 | 100.00% |
EXPORT_SYMBOL(__inode_permission);
/**
* sb_permission - Check superblock-level permissions
* @sb: Superblock of inode to check permission on
* @inode: Inode to check permission on
* @mask: Right to check for (%MAY_READ, %MAY_WRITE, %MAY_EXEC)
*
* Separate out file-system wide checks from inode-specific permission checks.
*/
static int sb_permission(struct super_block *sb, struct inode *inode, int mask)
{
if (unlikely(mask & MAY_WRITE)) {
umode_t mode = inode->i_mode;
/* Nobody gets write access to a read-only fs. */
if ((sb->s_flags & MS_RDONLY) &&
(S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode)))
return -EROFS;
}
return 0;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
david howells | david howells | 38 | 52.78% | 1 | 33.33% |
christoph hellwig | christoph hellwig | 27 | 37.50% | 1 | 33.33% |
miklos szeredi | miklos szeredi | 7 | 9.72% | 1 | 33.33% |
| Total | 72 | 100.00% | 3 | 100.00% |
/**
* inode_permission - Check for access rights to a given inode
* @inode: Inode to check permission on
* @mask: Right to check for (%MAY_READ, %MAY_WRITE, %MAY_EXEC)
*
* Check for read/write/execute permissions on an inode. We use fs[ug]id for
* this, letting us set arbitrary permissions for filesystem access without
* changing the "normal" UIDs which are used for other things.
*
* When checking for MAY_APPEND, MAY_WRITE must also be set in @mask.
*/
int inode_permission(struct inode *inode, int mask)
{
int retval;
retval = sb_permission(inode->i_sb, inode, mask);
if (retval)
return retval;
return __inode_permission(inode, mask);
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
david howells | david howells | 18 | 40.91% | 1 | 20.00% |
serge hallyn | serge hallyn | 15 | 34.09% | 1 | 20.00% |
pre-git | pre-git | 8 | 18.18% | 1 | 20.00% |
stephen d. smalley | stephen d. smalley | 2 | 4.55% | 1 | 20.00% |
al viro | al viro | 1 | 2.27% | 1 | 20.00% |
| Total | 44 | 100.00% | 5 | 100.00% |
EXPORT_SYMBOL(inode_permission);
/**
* path_get - get a reference to a path
* @path: path to get the reference to
*
* Given a path increment the reference count to the dentry and the vfsmount.
*/
void path_get(const struct path *path)
{
mntget(path->mnt);
dget(path->dentry);
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
jan blunck | jan blunck | 24 | 96.00% | 1 | 50.00% |
al viro | al viro | 1 | 4.00% | 1 | 50.00% |
| Total | 25 | 100.00% | 2 | 100.00% |
EXPORT_SYMBOL(path_get);
/**
* path_put - put a reference to a path
* @path: path to put the reference to
*
* Given a path decrement the reference count to the dentry and the vfsmount.
*/
void path_put(const struct path *path)
{
dput(path->dentry);
mntput(path->mnt);
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
pre-git | pre-git | 17 | 68.00% | 1 | 25.00% |
jan blunck | jan blunck | 7 | 28.00% | 2 | 50.00% |
al viro | al viro | 1 | 4.00% | 1 | 25.00% |
| Total | 25 | 100.00% | 4 | 100.00% |
EXPORT_SYMBOL(path_put);
#define EMBEDDED_LEVELS 2
struct nameidata {
struct path path;
struct qstr last;
struct path root;
struct inode *inode; /* path.dentry.d_inode */
unsigned int flags;
unsigned seq, m_seq;
int last_type;
unsigned depth;
int total_link_count;
struct saved {
struct path link;
struct delayed_call done;
const char *name;
unsigned seq;
}
*stack, internal[EMBEDDED_LEVELS];
struct filename *name;
struct nameidata *saved;
struct inode *link_inode;
unsigned root_seq;
int dfd;
};
static void set_nameidata(struct nameidata *p, int dfd, struct filename *name)
{
struct nameidata *old = current->nameidata;
p->stack = p->internal;
p->dfd = dfd;
p->name = name;
p->total_link_count = old ? old->total_link_count : 0;
p->saved = old;
current->nameidata = p;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
al viro | al viro | 42 | 58.33% | 3 | 75.00% |
neil brown | neil brown | 30 | 41.67% | 1 | 25.00% |
| Total | 72 | 100.00% | 4 | 100.00% |
static void restore_nameidata(void)
{
struct nameidata *now = current->nameidata, *old = now->saved;
current->nameidata = old;
if (old)
old->total_link_count = now->total_link_count;
if (now->stack != now->internal)
kfree(now->stack);
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
neil brown | neil brown | 30 | 50.85% | 1 | 33.33% |
al viro | al viro | 29 | 49.15% | 2 | 66.67% |
| Total | 59 | 100.00% | 3 | 100.00% |
static int __nd_alloc_stack(struct nameidata *nd)
{
struct saved *p;
if (nd->flags & LOOKUP_RCU) {
p= kmalloc(MAXSYMLINKS * sizeof(struct saved),
GFP_ATOMIC);
if (unlikely(!p))
return -ECHILD;
} else {
p= kmalloc(MAXSYMLINKS * sizeof(struct saved),
GFP_KERNEL);
if (unlikely(!p))
return -ENOMEM;
}
memcpy(p, nd->internal, sizeof(nd->internal));
nd->stack = p;
return 0;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
al viro | al viro | 108 | 100.00% | 2 | 100.00% |
| Total | 108 | 100.00% | 2 | 100.00% |
/**
* path_connected - Verify that a path->dentry is below path->mnt.mnt_root
* @path: nameidate to verify
*
* Rename can sometimes move a file or directory outside of a bind
* mount, path_connected allows those cases to be detected.
*/
static bool path_connected(const struct path *path)
{
struct vfsmount *mnt = path->mnt;
/* Only bind mounts can have disconnected paths */
if (mnt->mnt_root == mnt->mnt_sb->s_root)
return true;
return is_subdir(path->dentry, mnt->mnt_root);
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
eric w. biederman | eric w. biederman | 49 | 100.00% | 1 | 100.00% |
| Total | 49 | 100.00% | 1 | 100.00% |
static inline int nd_alloc_stack(struct nameidata *nd)
{
if (likely(nd->depth != EMBEDDED_LEVELS))
return 0;
if (likely(nd->stack != nd->internal))
return 0;
return __nd_alloc_stack(nd);
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
al viro | al viro | 48 | 100.00% | 1 | 100.00% |
| Total | 48 | 100.00% | 1 | 100.00% |
static void drop_links(struct nameidata *nd)
{
int i = nd->depth;
while (i--) {
struct saved *last = nd->stack + i;
do_delayed_call(&last->done);
clear_delayed_call(&last->done);
}
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
al viro | al viro | 52 | 100.00% | 2 | 100.00% |
| Total | 52 | 100.00% | 2 | 100.00% |
static void terminate_walk(struct nameidata *nd)
{
drop_links(nd);
if (!(nd->flags & LOOKUP_RCU)) {
int i;
path_put(&nd->path);
for (i = 0; i < nd->depth; i++)
path_put(&nd->stack[i].link);
if (nd->root.mnt && !(nd->flags & LOOKUP_ROOT)) {
path_put(&nd->root);
nd->root.mnt = NULL;
}
} else {
nd->flags &= ~LOOKUP_RCU;
if (!(nd->flags & LOOKUP_ROOT))
nd->root.mnt = NULL;
rcu_read_unlock();
}
nd->depth = 0;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
al viro | al viro | 141 | 100.00% | 2 | 100.00% |
| Total | 141 | 100.00% | 2 | 100.00% |
/* path_put is needed afterwards regardless of success or failure */
static bool legitimize_path(struct nameidata *nd,
struct path *path, unsigned seq)
{
int res = __legitimize_mnt(path->mnt, nd->m_seq);
if (unlikely(res)) {
if (res > 0)
path->mnt = NULL;
path->dentry = NULL;
return false;
}
if (unlikely(!lockref_get_not_dead(&path->dentry->d_lockref))) {
path->dentry = NULL;
return false;
}
return !read_seqcount_retry(&path->dentry->d_seq, seq);
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
al viro | al viro | 104 | 100.00% | 1 | 100.00% |
| Total | 104 | 100.00% | 1 | 100.00% |
static bool legitimize_links(struct nameidata *nd)
{
int i;
for (i = 0; i < nd->depth; i++) {
struct saved *last = nd->stack + i;
if (unlikely(!legitimize_path(nd, &last->link, last->seq))) {
drop_links(nd);
nd->depth = i + 1;
return false;
}
}
return true;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
al viro | al viro | 83 | 100.00% | 1 | 100.00% |
| Total | 83 | 100.00% | 1 | 100.00% |
/*
* Path walking has 2 modes, rcu-walk and ref-walk (see
* Documentation/filesystems/path-lookup.txt). In situations when we can't
* continue in RCU mode, we attempt to drop out of rcu-walk mode and grab
* normal reference counts on dentries and vfsmounts to transition to ref-walk
* mode. Refcounts are grabbed at the last known good point before rcu-walk
* got stuck, so ref-walk may continue from there. If this is not successful
* (eg. a seqcount has changed), then failure is returned and it's up to caller
* to restart the path walk from the beginning in ref-walk mode.
*/
/**
* unlazy_walk - try to switch to ref-walk mode.
* @nd: nameidata pathwalk data
* @dentry: child of nd->path.dentry or NULL
* @seq: seq number to check dentry against
* Returns: 0 on success, -ECHILD on failure
*
* unlazy_walk attempts to legitimize the current nd->path, nd->root and dentry
* for ref-walk mode. @dentry must be a path found by a do_lookup call on
* @nd or NULL. Must be called from rcu-walk context.
* Nothing should touch nameidata between unlazy_walk() failure and
* terminate_walk().
*/
static int unlazy_walk(struct nameidata *nd, struct dentry *dentry, unsigned seq)
{
struct dentry *parent = nd->path.dentry;
BUG_ON(!(nd->flags & LOOKUP_RCU));
nd->flags &= ~LOOKUP_RCU;
if (unlikely(!legitimize_links(nd)))
goto out2;
if (unlikely(!legitimize_mnt(nd->path.mnt, nd->m_seq)))
goto out2;
if (unlikely(!lockref_get_not_dead(&parent->d_lockref)))
goto out1;
/*
* For a negative lookup, the lookup sequence point is the parents
* sequence point, and it only needs to revalidate the parent dentry.
*
* For a positive lookup, we need to move both the parent and the
* dentry from the RCU domain to be properly refcounted. And the
* sequence number in the dentry validates *both* dentry counters,
* since we checked the sequence number of the parent after we got
* the child sequence number. So we know the parent must still
* be valid if the child sequence number is still valid.
*/
if (!dentry) {
if (read_seqcount_retry(&parent->d_seq, nd->seq))
goto out;
BUG_ON(nd->inode != parent->d_inode);
} else {
if (!lockref_get_not_dead(&dentry->d_lockref))
goto out;
if (read_seqcount_retry(&dentry->d_seq, seq))
goto drop_dentry;
}
/*
* Sequence counts matched. Now make sure that the root is
* still valid and get it if required.
*/
if (nd->root.mnt && !(nd->flags & LOOKUP_ROOT)) {
if (unlikely(!legitimize_path(nd, &nd->root, nd->root_seq))) {
rcu_read_unlock();
dput(dentry);
return -ECHILD;
}
}
rcu_read_unlock();
return 0;
drop_dentry:
rcu_read_unlock();
dput(dentry);
goto drop_root_mnt;
out2:
nd->path.mnt = NULL;
out1:
nd->path.dentry = NULL;
out:
rcu_read_unlock();
drop_root_mnt:
if (!(nd->flags & LOOKUP_ROOT))
nd->root.mnt = NULL;
return -ECHILD;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
al viro | al viro | 119 | 40.61% | 9 | 42.86% |
linus torvalds | linus torvalds | 88 | 30.03% | 4 | 19.05% |
nick piggin | nick piggin | 49 | 16.72% | 1 | 4.76% |
trond myklebust | trond myklebust | 14 | 4.78% | 1 | 4.76% |
ian kent | ian kent | 13 | 4.44% | 2 | 9.52% |
christoph hellwig | christoph hellwig | 7 | 2.39% | 2 | 9.52% |
josef 'jeff' sipek | josef 'jeff' sipek | 2 | 0.68% | 1 | 4.76% |
hanna linder | hanna linder | 1 | 0.34% | 1 | 4.76% |
| Total | 293 | 100.00% | 21 | 100.00% |
static int unlazy_link(struct nameidata *nd, struct path *link, unsigned seq)
{
if (unlikely(!legitimize_path(nd, link, seq))) {
drop_links(nd);
nd->depth = 0;
nd->flags &= ~LOOKUP_RCU;
nd->path.mnt = NULL;
nd->path.dentry = NULL;
if (!(nd->flags & LOOKUP_ROOT))
nd->root.mnt = NULL;
rcu_read_unlock();
} else if (likely(unlazy_walk(nd, NULL, 0)) == 0) {
return 0;
}
path_put(link);
return -ECHILD;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
al viro | al viro | 123 | 100.00% | 1 | 100.00% |
| Total | 123 | 100.00% | 1 | 100.00% |
static inline int d_revalidate(struct dentry *dentry, unsigned int flags)
{
return dentry->d_op->d_revalidate(dentry, flags);
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
nick piggin | nick piggin | 23 | 82.14% | 1 | 25.00% |
al viro | al viro | 5 | 17.86% | 3 | 75.00% |
| Total | 28 | 100.00% | 4 | 100.00% |
/**
* complete_walk - successful completion of path walk
* @nd: pointer nameidata
*
* If we had been in RCU mode, drop out of it and legitimize nd->path.
* Revalidate the final result, unless we'd already done that during
* the path walk or the filesystem doesn't ask for it. Return 0 on
* success, -error on failure. In case of failure caller does not
* need to drop nd->path.
*/
static int complete_walk(struct nameidata *nd)
{
struct dentry *dentry = nd->path.dentry;
int status;
if (nd->flags & LOOKUP_RCU) {
if (!(nd->flags & LOOKUP_ROOT))
nd->root.mnt = NULL;
if (unlikely(unlazy_walk(nd, NULL, 0)))
return -ECHILD;
}
if (likely(!(nd->flags & LOOKUP_JUMPED)))
return 0;
if (likely(!(dentry->d_flags & DCACHE_OP_WEAK_REVALIDATE)))
return 0;
status = dentry->d_op->d_weak_revalidate(dentry, nd->flags);
if (status > 0)
return 0;
if (!status)
status = -