|
@@ -136,7 +136,7 @@ static int do_getname(const char __user *filename, char *page)
|
|
|
return retval;
|
|
|
}
|
|
|
|
|
|
-char * getname(const char __user * filename)
|
|
|
+static char *getname_flags(const char __user * filename, int flags)
|
|
|
{
|
|
|
char *tmp, *result;
|
|
|
|
|
@@ -147,14 +147,21 @@ char * getname(const char __user * filename)
|
|
|
|
|
|
result = tmp;
|
|
|
if (retval < 0) {
|
|
|
- __putname(tmp);
|
|
|
- result = ERR_PTR(retval);
|
|
|
+ if (retval != -ENOENT || !(flags & LOOKUP_EMPTY)) {
|
|
|
+ __putname(tmp);
|
|
|
+ result = ERR_PTR(retval);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
audit_getname(result);
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
+char *getname(const char __user * filename)
|
|
|
+{
|
|
|
+ return getname_flags(filename, 0);
|
|
|
+}
|
|
|
+
|
|
|
#ifdef CONFIG_AUDITSYSCALL
|
|
|
void putname(const char *name)
|
|
|
{
|
|
@@ -401,9 +408,11 @@ static int nameidata_drop_rcu(struct nameidata *nd)
|
|
|
{
|
|
|
struct fs_struct *fs = current->fs;
|
|
|
struct dentry *dentry = nd->path.dentry;
|
|
|
+ int want_root = 0;
|
|
|
|
|
|
BUG_ON(!(nd->flags & LOOKUP_RCU));
|
|
|
- if (nd->root.mnt) {
|
|
|
+ if (nd->root.mnt && !(nd->flags & LOOKUP_ROOT)) {
|
|
|
+ want_root = 1;
|
|
|
spin_lock(&fs->lock);
|
|
|
if (nd->root.mnt != fs->root.mnt ||
|
|
|
nd->root.dentry != fs->root.dentry)
|
|
@@ -414,7 +423,7 @@ static int nameidata_drop_rcu(struct nameidata *nd)
|
|
|
goto err;
|
|
|
BUG_ON(nd->inode != dentry->d_inode);
|
|
|
spin_unlock(&dentry->d_lock);
|
|
|
- if (nd->root.mnt) {
|
|
|
+ if (want_root) {
|
|
|
path_get(&nd->root);
|
|
|
spin_unlock(&fs->lock);
|
|
|
}
|
|
@@ -427,7 +436,7 @@ static int nameidata_drop_rcu(struct nameidata *nd)
|
|
|
err:
|
|
|
spin_unlock(&dentry->d_lock);
|
|
|
err_root:
|
|
|
- if (nd->root.mnt)
|
|
|
+ if (want_root)
|
|
|
spin_unlock(&fs->lock);
|
|
|
return -ECHILD;
|
|
|
}
|
|
@@ -454,9 +463,11 @@ static int nameidata_dentry_drop_rcu(struct nameidata *nd, struct dentry *dentry
|
|
|
{
|
|
|
struct fs_struct *fs = current->fs;
|
|
|
struct dentry *parent = nd->path.dentry;
|
|
|
+ int want_root = 0;
|
|
|
|
|
|
BUG_ON(!(nd->flags & LOOKUP_RCU));
|
|
|
- if (nd->root.mnt) {
|
|
|
+ if (nd->root.mnt && !(nd->flags & LOOKUP_ROOT)) {
|
|
|
+ want_root = 1;
|
|
|
spin_lock(&fs->lock);
|
|
|
if (nd->root.mnt != fs->root.mnt ||
|
|
|
nd->root.dentry != fs->root.dentry)
|
|
@@ -476,7 +487,7 @@ static int nameidata_dentry_drop_rcu(struct nameidata *nd, struct dentry *dentry
|
|
|
parent->d_count++;
|
|
|
spin_unlock(&dentry->d_lock);
|
|
|
spin_unlock(&parent->d_lock);
|
|
|
- if (nd->root.mnt) {
|
|
|
+ if (want_root) {
|
|
|
path_get(&nd->root);
|
|
|
spin_unlock(&fs->lock);
|
|
|
}
|
|
@@ -490,7 +501,7 @@ err:
|
|
|
spin_unlock(&dentry->d_lock);
|
|
|
spin_unlock(&parent->d_lock);
|
|
|
err_root:
|
|
|
- if (nd->root.mnt)
|
|
|
+ if (want_root)
|
|
|
spin_unlock(&fs->lock);
|
|
|
return -ECHILD;
|
|
|
}
|
|
@@ -498,8 +509,16 @@ err_root:
|
|
|
/* Try to drop out of rcu-walk mode if we were in it, otherwise do nothing. */
|
|
|
static inline int nameidata_dentry_drop_rcu_maybe(struct nameidata *nd, struct dentry *dentry)
|
|
|
{
|
|
|
- if (nd->flags & LOOKUP_RCU)
|
|
|
- return nameidata_dentry_drop_rcu(nd, dentry);
|
|
|
+ if (nd->flags & LOOKUP_RCU) {
|
|
|
+ if (unlikely(nameidata_dentry_drop_rcu(nd, dentry))) {
|
|
|
+ nd->flags &= ~LOOKUP_RCU;
|
|
|
+ if (!(nd->flags & LOOKUP_ROOT))
|
|
|
+ nd->root.mnt = NULL;
|
|
|
+ rcu_read_unlock();
|
|
|
+ br_read_unlock(vfsmount_lock);
|
|
|
+ return -ECHILD;
|
|
|
+ }
|
|
|
+ }
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
@@ -518,7 +537,8 @@ static int nameidata_drop_rcu_last(struct nameidata *nd)
|
|
|
|
|
|
BUG_ON(!(nd->flags & LOOKUP_RCU));
|
|
|
nd->flags &= ~LOOKUP_RCU;
|
|
|
- nd->root.mnt = NULL;
|
|
|
+ if (!(nd->flags & LOOKUP_ROOT))
|
|
|
+ nd->root.mnt = NULL;
|
|
|
spin_lock(&dentry->d_lock);
|
|
|
if (!__d_rcu_to_refcount(dentry, nd->seq))
|
|
|
goto err_unlock;
|
|
@@ -539,14 +559,6 @@ err_unlock:
|
|
|
return -ECHILD;
|
|
|
}
|
|
|
|
|
|
-/* Try to drop out of rcu-walk mode if we were in it, otherwise do nothing. */
|
|
|
-static inline int nameidata_drop_rcu_last_maybe(struct nameidata *nd)
|
|
|
-{
|
|
|
- if (likely(nd->flags & LOOKUP_RCU))
|
|
|
- return nameidata_drop_rcu_last(nd);
|
|
|
- return 0;
|
|
|
-}
|
|
|
-
|
|
|
/**
|
|
|
* release_open_intent - free up open intent resources
|
|
|
* @nd: pointer to nameidata
|
|
@@ -590,42 +602,8 @@ do_revalidate(struct dentry *dentry, struct nameidata *nd)
|
|
|
return dentry;
|
|
|
}
|
|
|
|
|
|
-static inline struct dentry *
|
|
|
-do_revalidate_rcu(struct dentry *dentry, struct nameidata *nd)
|
|
|
-{
|
|
|
- int status = d_revalidate(dentry, nd);
|
|
|
- if (likely(status > 0))
|
|
|
- return dentry;
|
|
|
- if (status == -ECHILD) {
|
|
|
- if (nameidata_dentry_drop_rcu(nd, dentry))
|
|
|
- return ERR_PTR(-ECHILD);
|
|
|
- return do_revalidate(dentry, nd);
|
|
|
- }
|
|
|
- if (status < 0)
|
|
|
- return ERR_PTR(status);
|
|
|
- /* Don't d_invalidate in rcu-walk mode */
|
|
|
- if (nameidata_dentry_drop_rcu(nd, dentry))
|
|
|
- return ERR_PTR(-ECHILD);
|
|
|
- if (!d_invalidate(dentry)) {
|
|
|
- dput(dentry);
|
|
|
- dentry = NULL;
|
|
|
- }
|
|
|
- return dentry;
|
|
|
-}
|
|
|
-
|
|
|
-static inline int need_reval_dot(struct dentry *dentry)
|
|
|
-{
|
|
|
- if (likely(!(dentry->d_flags & DCACHE_OP_REVALIDATE)))
|
|
|
- return 0;
|
|
|
-
|
|
|
- if (likely(!(dentry->d_sb->s_type->fs_flags & FS_REVAL_DOT)))
|
|
|
- return 0;
|
|
|
-
|
|
|
- return 1;
|
|
|
-}
|
|
|
-
|
|
|
/*
|
|
|
- * force_reval_path - force revalidation of a dentry
|
|
|
+ * handle_reval_path - force revalidation of a dentry
|
|
|
*
|
|
|
* In some situations the path walking code will trust dentries without
|
|
|
* revalidating them. This causes problems for filesystems that depend on
|
|
@@ -639,27 +617,28 @@ static inline int need_reval_dot(struct dentry *dentry)
|
|
|
* invalidate the dentry. It's up to the caller to handle putting references
|
|
|
* to the path if necessary.
|
|
|
*/
|
|
|
-static int
|
|
|
-force_reval_path(struct path *path, struct nameidata *nd)
|
|
|
+static inline int handle_reval_path(struct nameidata *nd)
|
|
|
{
|
|
|
+ struct dentry *dentry = nd->path.dentry;
|
|
|
int status;
|
|
|
- struct dentry *dentry = path->dentry;
|
|
|
|
|
|
- /*
|
|
|
- * only check on filesystems where it's possible for the dentry to
|
|
|
- * become stale.
|
|
|
- */
|
|
|
- if (!need_reval_dot(dentry))
|
|
|
+ if (likely(!(nd->flags & LOOKUP_JUMPED)))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ if (likely(!(dentry->d_flags & DCACHE_OP_REVALIDATE)))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ if (likely(!(dentry->d_sb->s_type->fs_flags & FS_REVAL_DOT)))
|
|
|
return 0;
|
|
|
|
|
|
+ /* Note: we do not d_invalidate() */
|
|
|
status = d_revalidate(dentry, nd);
|
|
|
if (status > 0)
|
|
|
return 0;
|
|
|
|
|
|
- if (!status) {
|
|
|
- d_invalidate(dentry);
|
|
|
+ if (!status)
|
|
|
status = -ESTALE;
|
|
|
- }
|
|
|
+
|
|
|
return status;
|
|
|
}
|
|
|
|
|
@@ -728,6 +707,7 @@ static __always_inline int __vfs_follow_link(struct nameidata *nd, const char *l
|
|
|
path_put(&nd->path);
|
|
|
nd->path = nd->root;
|
|
|
path_get(&nd->root);
|
|
|
+ nd->flags |= LOOKUP_JUMPED;
|
|
|
}
|
|
|
nd->inode = nd->path.dentry->d_inode;
|
|
|
|
|
@@ -757,20 +737,44 @@ static inline void path_to_nameidata(const struct path *path,
|
|
|
nd->path.dentry = path->dentry;
|
|
|
}
|
|
|
|
|
|
+static inline void put_link(struct nameidata *nd, struct path *link, void *cookie)
|
|
|
+{
|
|
|
+ struct inode *inode = link->dentry->d_inode;
|
|
|
+ if (!IS_ERR(cookie) && inode->i_op->put_link)
|
|
|
+ inode->i_op->put_link(link->dentry, nd, cookie);
|
|
|
+ path_put(link);
|
|
|
+}
|
|
|
+
|
|
|
static __always_inline int
|
|
|
-__do_follow_link(const struct path *link, struct nameidata *nd, void **p)
|
|
|
+follow_link(struct path *link, struct nameidata *nd, void **p)
|
|
|
{
|
|
|
int error;
|
|
|
struct dentry *dentry = link->dentry;
|
|
|
|
|
|
BUG_ON(nd->flags & LOOKUP_RCU);
|
|
|
|
|
|
+ if (unlikely(current->total_link_count >= 40)) {
|
|
|
+ *p = ERR_PTR(-ELOOP); /* no ->put_link(), please */
|
|
|
+ path_put_conditional(link, nd);
|
|
|
+ path_put(&nd->path);
|
|
|
+ return -ELOOP;
|
|
|
+ }
|
|
|
+ cond_resched();
|
|
|
+ current->total_link_count++;
|
|
|
+
|
|
|
touch_atime(link->mnt, dentry);
|
|
|
nd_set_link(nd, NULL);
|
|
|
|
|
|
if (link->mnt == nd->path.mnt)
|
|
|
mntget(link->mnt);
|
|
|
|
|
|
+ error = security_inode_follow_link(link->dentry, nd);
|
|
|
+ if (error) {
|
|
|
+ *p = ERR_PTR(error); /* no ->put_link(), please */
|
|
|
+ path_put(&nd->path);
|
|
|
+ return error;
|
|
|
+ }
|
|
|
+
|
|
|
nd->last_type = LAST_BIND;
|
|
|
*p = dentry->d_inode->i_op->follow_link(dentry, nd);
|
|
|
error = PTR_ERR(*p);
|
|
@@ -780,56 +784,18 @@ __do_follow_link(const struct path *link, struct nameidata *nd, void **p)
|
|
|
if (s)
|
|
|
error = __vfs_follow_link(nd, s);
|
|
|
else if (nd->last_type == LAST_BIND) {
|
|
|
- error = force_reval_path(&nd->path, nd);
|
|
|
- if (error)
|
|
|
+ nd->flags |= LOOKUP_JUMPED;
|
|
|
+ nd->inode = nd->path.dentry->d_inode;
|
|
|
+ if (nd->inode->i_op->follow_link) {
|
|
|
+ /* stepped on a _really_ weird one */
|
|
|
path_put(&nd->path);
|
|
|
+ error = -ELOOP;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
return error;
|
|
|
}
|
|
|
|
|
|
-/*
|
|
|
- * This limits recursive symlink follows to 8, while
|
|
|
- * limiting consecutive symlinks to 40.
|
|
|
- *
|
|
|
- * Without that kind of total limit, nasty chains of consecutive
|
|
|
- * symlinks can cause almost arbitrarily long lookups.
|
|
|
- */
|
|
|
-static inline int do_follow_link(struct inode *inode, struct path *path, struct nameidata *nd)
|
|
|
-{
|
|
|
- void *cookie;
|
|
|
- int err = -ELOOP;
|
|
|
-
|
|
|
- /* We drop rcu-walk here */
|
|
|
- if (nameidata_dentry_drop_rcu_maybe(nd, path->dentry))
|
|
|
- return -ECHILD;
|
|
|
- BUG_ON(inode != path->dentry->d_inode);
|
|
|
-
|
|
|
- if (current->link_count >= MAX_NESTED_LINKS)
|
|
|
- goto loop;
|
|
|
- if (current->total_link_count >= 40)
|
|
|
- goto loop;
|
|
|
- BUG_ON(nd->depth >= MAX_NESTED_LINKS);
|
|
|
- cond_resched();
|
|
|
- err = security_inode_follow_link(path->dentry, nd);
|
|
|
- if (err)
|
|
|
- goto loop;
|
|
|
- current->link_count++;
|
|
|
- current->total_link_count++;
|
|
|
- nd->depth++;
|
|
|
- err = __do_follow_link(path, nd, &cookie);
|
|
|
- if (!IS_ERR(cookie) && path->dentry->d_inode->i_op->put_link)
|
|
|
- path->dentry->d_inode->i_op->put_link(path->dentry, nd, cookie);
|
|
|
- path_put(path);
|
|
|
- current->link_count--;
|
|
|
- nd->depth--;
|
|
|
- return err;
|
|
|
-loop:
|
|
|
- path_put_conditional(path, nd);
|
|
|
- path_put(&nd->path);
|
|
|
- return err;
|
|
|
-}
|
|
|
-
|
|
|
static int follow_up_rcu(struct path *path)
|
|
|
{
|
|
|
struct vfsmount *parent;
|
|
@@ -1068,7 +1034,7 @@ static int follow_dotdot_rcu(struct nameidata *nd)
|
|
|
|
|
|
seq = read_seqcount_begin(&parent->d_seq);
|
|
|
if (read_seqcount_retry(&old->d_seq, nd->seq))
|
|
|
- return -ECHILD;
|
|
|
+ goto failed;
|
|
|
inode = parent->d_inode;
|
|
|
nd->path.dentry = parent;
|
|
|
nd->seq = seq;
|
|
@@ -1081,8 +1047,15 @@ static int follow_dotdot_rcu(struct nameidata *nd)
|
|
|
}
|
|
|
__follow_mount_rcu(nd, &nd->path, &inode, true);
|
|
|
nd->inode = inode;
|
|
|
-
|
|
|
return 0;
|
|
|
+
|
|
|
+failed:
|
|
|
+ nd->flags &= ~LOOKUP_RCU;
|
|
|
+ if (!(nd->flags & LOOKUP_ROOT))
|
|
|
+ nd->root.mnt = NULL;
|
|
|
+ rcu_read_unlock();
|
|
|
+ br_read_unlock(vfsmount_lock);
|
|
|
+ return -ECHILD;
|
|
|
}
|
|
|
|
|
|
/*
|
|
@@ -1216,19 +1189,10 @@ static int do_lookup(struct nameidata *nd, struct qstr *name,
|
|
|
{
|
|
|
struct vfsmount *mnt = nd->path.mnt;
|
|
|
struct dentry *dentry, *parent = nd->path.dentry;
|
|
|
- struct inode *dir;
|
|
|
+ int need_reval = 1;
|
|
|
+ int status = 1;
|
|
|
int err;
|
|
|
|
|
|
- /*
|
|
|
- * See if the low-level filesystem might want
|
|
|
- * to use its own hash..
|
|
|
- */
|
|
|
- if (unlikely(parent->d_flags & DCACHE_OP_HASH)) {
|
|
|
- err = parent->d_op->d_hash(parent, nd->inode, name);
|
|
|
- if (err < 0)
|
|
|
- return err;
|
|
|
- }
|
|
|
-
|
|
|
/*
|
|
|
* Rename seqlock is not required here because in the off chance
|
|
|
* of a false negative due to a concurrent rename, we're going to
|
|
@@ -1236,48 +1200,74 @@ static int do_lookup(struct nameidata *nd, struct qstr *name,
|
|
|
*/
|
|
|
if (nd->flags & LOOKUP_RCU) {
|
|
|
unsigned seq;
|
|
|
-
|
|
|
*inode = nd->inode;
|
|
|
dentry = __d_lookup_rcu(parent, name, &seq, inode);
|
|
|
- if (!dentry) {
|
|
|
- if (nameidata_drop_rcu(nd))
|
|
|
- return -ECHILD;
|
|
|
- goto need_lookup;
|
|
|
- }
|
|
|
+ if (!dentry)
|
|
|
+ goto unlazy;
|
|
|
+
|
|
|
/* Memory barrier in read_seqcount_begin of child is enough */
|
|
|
if (__read_seqcount_retry(&parent->d_seq, nd->seq))
|
|
|
return -ECHILD;
|
|
|
-
|
|
|
nd->seq = seq;
|
|
|
+
|
|
|
if (unlikely(dentry->d_flags & DCACHE_OP_REVALIDATE)) {
|
|
|
- dentry = do_revalidate_rcu(dentry, nd);
|
|
|
- if (!dentry)
|
|
|
- goto need_lookup;
|
|
|
- if (IS_ERR(dentry))
|
|
|
- goto fail;
|
|
|
- if (!(nd->flags & LOOKUP_RCU))
|
|
|
- goto done;
|
|
|
+ status = d_revalidate(dentry, nd);
|
|
|
+ if (unlikely(status <= 0)) {
|
|
|
+ if (status != -ECHILD)
|
|
|
+ need_reval = 0;
|
|
|
+ goto unlazy;
|
|
|
+ }
|
|
|
}
|
|
|
path->mnt = mnt;
|
|
|
path->dentry = dentry;
|
|
|
if (likely(__follow_mount_rcu(nd, path, inode, false)))
|
|
|
return 0;
|
|
|
- if (nameidata_drop_rcu(nd))
|
|
|
- return -ECHILD;
|
|
|
- /* fallthru */
|
|
|
+unlazy:
|
|
|
+ if (dentry) {
|
|
|
+ if (nameidata_dentry_drop_rcu(nd, dentry))
|
|
|
+ return -ECHILD;
|
|
|
+ } else {
|
|
|
+ if (nameidata_drop_rcu(nd))
|
|
|
+ return -ECHILD;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ dentry = __d_lookup(parent, name);
|
|
|
}
|
|
|
- dentry = __d_lookup(parent, name);
|
|
|
- if (!dentry)
|
|
|
- goto need_lookup;
|
|
|
-found:
|
|
|
- if (unlikely(dentry->d_flags & DCACHE_OP_REVALIDATE)) {
|
|
|
- dentry = do_revalidate(dentry, nd);
|
|
|
- if (!dentry)
|
|
|
- goto need_lookup;
|
|
|
- if (IS_ERR(dentry))
|
|
|
- goto fail;
|
|
|
+
|
|
|
+retry:
|
|
|
+ if (unlikely(!dentry)) {
|
|
|
+ struct inode *dir = parent->d_inode;
|
|
|
+ BUG_ON(nd->inode != dir);
|
|
|
+
|
|
|
+ mutex_lock(&dir->i_mutex);
|
|
|
+ dentry = d_lookup(parent, name);
|
|
|
+ if (likely(!dentry)) {
|
|
|
+ dentry = d_alloc_and_lookup(parent, name, nd);
|
|
|
+ if (IS_ERR(dentry)) {
|
|
|
+ mutex_unlock(&dir->i_mutex);
|
|
|
+ return PTR_ERR(dentry);
|
|
|
+ }
|
|
|
+ /* known good */
|
|
|
+ need_reval = 0;
|
|
|
+ status = 1;
|
|
|
+ }
|
|
|
+ mutex_unlock(&dir->i_mutex);
|
|
|
}
|
|
|
-done:
|
|
|
+ if (unlikely(dentry->d_flags & DCACHE_OP_REVALIDATE) && need_reval)
|
|
|
+ status = d_revalidate(dentry, nd);
|
|
|
+ if (unlikely(status <= 0)) {
|
|
|
+ if (status < 0) {
|
|
|
+ dput(dentry);
|
|
|
+ return status;
|
|
|
+ }
|
|
|
+ if (!d_invalidate(dentry)) {
|
|
|
+ dput(dentry);
|
|
|
+ dentry = NULL;
|
|
|
+ need_reval = 1;
|
|
|
+ goto retry;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
path->mnt = mnt;
|
|
|
path->dentry = dentry;
|
|
|
err = follow_managed(path, nd->flags);
|
|
@@ -1287,39 +1277,113 @@ done:
|
|
|
}
|
|
|
*inode = path->dentry->d_inode;
|
|
|
return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static inline int may_lookup(struct nameidata *nd)
|
|
|
+{
|
|
|
+ if (nd->flags & LOOKUP_RCU) {
|
|
|
+ int err = exec_permission(nd->inode, IPERM_FLAG_RCU);
|
|
|
+ if (err != -ECHILD)
|
|
|
+ return err;
|
|
|
+ if (nameidata_drop_rcu(nd))
|
|
|
+ return -ECHILD;
|
|
|
+ }
|
|
|
+ return exec_permission(nd->inode, 0);
|
|
|
+}
|
|
|
|
|
|
-need_lookup:
|
|
|
- dir = parent->d_inode;
|
|
|
- BUG_ON(nd->inode != dir);
|
|
|
+static inline int handle_dots(struct nameidata *nd, int type)
|
|
|
+{
|
|
|
+ if (type == LAST_DOTDOT) {
|
|
|
+ if (nd->flags & LOOKUP_RCU) {
|
|
|
+ if (follow_dotdot_rcu(nd))
|
|
|
+ return -ECHILD;
|
|
|
+ } else
|
|
|
+ follow_dotdot(nd);
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
|
|
|
- mutex_lock(&dir->i_mutex);
|
|
|
- /*
|
|
|
- * First re-do the cached lookup just in case it was created
|
|
|
- * while we waited for the directory semaphore, or the first
|
|
|
- * lookup failed due to an unrelated rename.
|
|
|
- *
|
|
|
- * This could use version numbering or similar to avoid unnecessary
|
|
|
- * cache lookups, but then we'd have to do the first lookup in the
|
|
|
- * non-racy way. However in the common case here, everything should
|
|
|
- * be hot in cache, so would it be a big win?
|
|
|
- */
|
|
|
- dentry = d_lookup(parent, name);
|
|
|
- if (likely(!dentry)) {
|
|
|
- dentry = d_alloc_and_lookup(parent, name, nd);
|
|
|
- mutex_unlock(&dir->i_mutex);
|
|
|
- if (IS_ERR(dentry))
|
|
|
- goto fail;
|
|
|
- goto done;
|
|
|
+static void terminate_walk(struct nameidata *nd)
|
|
|
+{
|
|
|
+ if (!(nd->flags & LOOKUP_RCU)) {
|
|
|
+ path_put(&nd->path);
|
|
|
+ } else {
|
|
|
+ nd->flags &= ~LOOKUP_RCU;
|
|
|
+ if (!(nd->flags & LOOKUP_ROOT))
|
|
|
+ nd->root.mnt = NULL;
|
|
|
+ rcu_read_unlock();
|
|
|
+ br_read_unlock(vfsmount_lock);
|
|
|
}
|
|
|
+}
|
|
|
+
|
|
|
+static inline int walk_component(struct nameidata *nd, struct path *path,
|
|
|
+ struct qstr *name, int type, int follow)
|
|
|
+{
|
|
|
+ struct inode *inode;
|
|
|
+ int err;
|
|
|
/*
|
|
|
- * Uhhuh! Nasty case: the cache was re-populated while
|
|
|
- * we waited on the semaphore. Need to revalidate.
|
|
|
+ * "." and ".." are special - ".." especially so because it has
|
|
|
+ * to be able to know about the current root directory and
|
|
|
+ * parent relationships.
|
|
|
*/
|
|
|
- mutex_unlock(&dir->i_mutex);
|
|
|
- goto found;
|
|
|
+ if (unlikely(type != LAST_NORM))
|
|
|
+ return handle_dots(nd, type);
|
|
|
+ err = do_lookup(nd, name, path, &inode);
|
|
|
+ if (unlikely(err)) {
|
|
|
+ terminate_walk(nd);
|
|
|
+ return err;
|
|
|
+ }
|
|
|
+ if (!inode) {
|
|
|
+ path_to_nameidata(path, nd);
|
|
|
+ terminate_walk(nd);
|
|
|
+ return -ENOENT;
|
|
|
+ }
|
|
|
+ if (unlikely(inode->i_op->follow_link) && follow) {
|
|
|
+ if (nameidata_dentry_drop_rcu_maybe(nd, path->dentry))
|
|
|
+ return -ECHILD;
|
|
|
+ BUG_ON(inode != path->dentry->d_inode);
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ path_to_nameidata(path, nd);
|
|
|
+ nd->inode = inode;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
|
|
|
-fail:
|
|
|
- return PTR_ERR(dentry);
|
|
|
+/*
|
|
|
+ * This limits recursive symlink follows to 8, while
|
|
|
+ * limiting consecutive symlinks to 40.
|
|
|
+ *
|
|
|
+ * Without that kind of total limit, nasty chains of consecutive
|
|
|
+ * symlinks can cause almost arbitrarily long lookups.
|
|
|
+ */
|
|
|
+static inline int nested_symlink(struct path *path, struct nameidata *nd)
|
|
|
+{
|
|
|
+ int res;
|
|
|
+
|
|
|
+ BUG_ON(nd->depth >= MAX_NESTED_LINKS);
|
|
|
+ if (unlikely(current->link_count >= MAX_NESTED_LINKS)) {
|
|
|
+ path_put_conditional(path, nd);
|
|
|
+ path_put(&nd->path);
|
|
|
+ return -ELOOP;
|
|
|
+ }
|
|
|
+
|
|
|
+ nd->depth++;
|
|
|
+ current->link_count++;
|
|
|
+
|
|
|
+ do {
|
|
|
+ struct path link = *path;
|
|
|
+ void *cookie;
|
|
|
+
|
|
|
+ res = follow_link(&link, nd, &cookie);
|
|
|
+ if (!res)
|
|
|
+ res = walk_component(nd, path, &nd->last,
|
|
|
+ nd->last_type, LOOKUP_FOLLOW);
|
|
|
+ put_link(nd, &link, cookie);
|
|
|
+ } while (res > 0);
|
|
|
+
|
|
|
+ current->link_count--;
|
|
|
+ nd->depth--;
|
|
|
+ return res;
|
|
|
}
|
|
|
|
|
|
/*
|
|
@@ -1339,30 +1403,18 @@ static int link_path_walk(const char *name, struct nameidata *nd)
|
|
|
while (*name=='/')
|
|
|
name++;
|
|
|
if (!*name)
|
|
|
- goto return_reval;
|
|
|
-
|
|
|
- if (nd->depth)
|
|
|
- lookup_flags = LOOKUP_FOLLOW | (nd->flags & LOOKUP_CONTINUE);
|
|
|
+ return 0;
|
|
|
|
|
|
/* At this point we know we have a real path component. */
|
|
|
for(;;) {
|
|
|
- struct inode *inode;
|
|
|
unsigned long hash;
|
|
|
struct qstr this;
|
|
|
unsigned int c;
|
|
|
+ int type;
|
|
|
|
|
|
nd->flags |= LOOKUP_CONTINUE;
|
|
|
- if (nd->flags & LOOKUP_RCU) {
|
|
|
- err = exec_permission(nd->inode, IPERM_FLAG_RCU);
|
|
|
- if (err == -ECHILD) {
|
|
|
- if (nameidata_drop_rcu(nd))
|
|
|
- return -ECHILD;
|
|
|
- goto exec_again;
|
|
|
- }
|
|
|
- } else {
|
|
|
-exec_again:
|
|
|
- err = exec_permission(nd->inode, 0);
|
|
|
- }
|
|
|
+
|
|
|
+ err = may_lookup(nd);
|
|
|
if (err)
|
|
|
break;
|
|
|
|
|
@@ -1378,52 +1430,43 @@ exec_again:
|
|
|
this.len = name - (const char *) this.name;
|
|
|
this.hash = end_name_hash(hash);
|
|
|
|
|
|
+ type = LAST_NORM;
|
|
|
+ if (this.name[0] == '.') switch (this.len) {
|
|
|
+ case 2:
|
|
|
+ if (this.name[1] == '.') {
|
|
|
+ type = LAST_DOTDOT;
|
|
|
+ nd->flags |= LOOKUP_JUMPED;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 1:
|
|
|
+ type = LAST_DOT;
|
|
|
+ }
|
|
|
+ if (likely(type == LAST_NORM)) {
|
|
|
+ struct dentry *parent = nd->path.dentry;
|
|
|
+ nd->flags &= ~LOOKUP_JUMPED;
|
|
|
+ if (unlikely(parent->d_flags & DCACHE_OP_HASH)) {
|
|
|
+ err = parent->d_op->d_hash(parent, nd->inode,
|
|
|
+ &this);
|
|
|
+ if (err < 0)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/* remove trailing slashes? */
|
|
|
if (!c)
|
|
|
goto last_component;
|
|
|
while (*++name == '/');
|
|
|
if (!*name)
|
|
|
- goto last_with_slashes;
|
|
|
+ goto last_component;
|
|
|
|
|
|
- /*
|
|
|
- * "." and ".." are special - ".." especially so because it has
|
|
|
- * to be able to know about the current root directory and
|
|
|
- * parent relationships.
|
|
|
- */
|
|
|
- if (this.name[0] == '.') switch (this.len) {
|
|
|
- default:
|
|
|
- break;
|
|
|
- case 2:
|
|
|
- if (this.name[1] != '.')
|
|
|
- break;
|
|
|
- if (nd->flags & LOOKUP_RCU) {
|
|
|
- if (follow_dotdot_rcu(nd))
|
|
|
- return -ECHILD;
|
|
|
- } else
|
|
|
- follow_dotdot(nd);
|
|
|
- /* fallthrough */
|
|
|
- case 1:
|
|
|
- continue;
|
|
|
- }
|
|
|
- /* This does the actual lookups.. */
|
|
|
- err = do_lookup(nd, &this, &next, &inode);
|
|
|
- if (err)
|
|
|
- break;
|
|
|
- err = -ENOENT;
|
|
|
- if (!inode)
|
|
|
- goto out_dput;
|
|
|
+ err = walk_component(nd, &next, &this, type, LOOKUP_FOLLOW);
|
|
|
+ if (err < 0)
|
|
|
+ return err;
|
|
|
|
|
|
- if (inode->i_op->follow_link) {
|
|
|
- err = do_follow_link(inode, &next, nd);
|
|
|
+ if (err) {
|
|
|
+ err = nested_symlink(&next, nd);
|
|
|
if (err)
|
|
|
- goto return_err;
|
|
|
- nd->inode = nd->path.dentry->d_inode;
|
|
|
- err = -ENOENT;
|
|
|
- if (!nd->inode)
|
|
|
- break;
|
|
|
- } else {
|
|
|
- path_to_nameidata(&next, nd);
|
|
|
- nd->inode = inode;
|
|
|
+ return err;
|
|
|
}
|
|
|
err = -ENOTDIR;
|
|
|
if (!nd->inode->i_op->lookup)
|
|
@@ -1431,210 +1474,109 @@ exec_again:
|
|
|
continue;
|
|
|
/* here ends the main loop */
|
|
|
|
|
|
-last_with_slashes:
|
|
|
- lookup_flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
|
|
|
last_component:
|
|
|
/* Clear LOOKUP_CONTINUE iff it was previously unset */
|
|
|
nd->flags &= lookup_flags | ~LOOKUP_CONTINUE;
|
|
|
- if (lookup_flags & LOOKUP_PARENT)
|
|
|
- goto lookup_parent;
|
|
|
- if (this.name[0] == '.') switch (this.len) {
|
|
|
- default:
|
|
|
- break;
|
|
|
- case 2:
|
|
|
- if (this.name[1] != '.')
|
|
|
- break;
|
|
|
- if (nd->flags & LOOKUP_RCU) {
|
|
|
- if (follow_dotdot_rcu(nd))
|
|
|
- return -ECHILD;
|
|
|
- } else
|
|
|
- follow_dotdot(nd);
|
|
|
- /* fallthrough */
|
|
|
- case 1:
|
|
|
- goto return_reval;
|
|
|
- }
|
|
|
- err = do_lookup(nd, &this, &next, &inode);
|
|
|
- if (err)
|
|
|
- break;
|
|
|
- if (inode && unlikely(inode->i_op->follow_link) &&
|
|
|
- (lookup_flags & LOOKUP_FOLLOW)) {
|
|
|
- err = do_follow_link(inode, &next, nd);
|
|
|
- if (err)
|
|
|
- goto return_err;
|
|
|
- nd->inode = nd->path.dentry->d_inode;
|
|
|
- } else {
|
|
|
- path_to_nameidata(&next, nd);
|
|
|
- nd->inode = inode;
|
|
|
- }
|
|
|
- err = -ENOENT;
|
|
|
- if (!nd->inode)
|
|
|
- break;
|
|
|
- if (lookup_flags & LOOKUP_DIRECTORY) {
|
|
|
- err = -ENOTDIR;
|
|
|
- if (!nd->inode->i_op->lookup)
|
|
|
- break;
|
|
|
- }
|
|
|
- goto return_base;
|
|
|
-lookup_parent:
|
|
|
nd->last = this;
|
|
|
- nd->last_type = LAST_NORM;
|
|
|
- if (this.name[0] != '.')
|
|
|
- goto return_base;
|
|
|
- if (this.len == 1)
|
|
|
- nd->last_type = LAST_DOT;
|
|
|
- else if (this.len == 2 && this.name[1] == '.')
|
|
|
- nd->last_type = LAST_DOTDOT;
|
|
|
- else
|
|
|
- goto return_base;
|
|
|
-return_reval:
|
|
|
- /*
|
|
|
- * We bypassed the ordinary revalidation routines.
|
|
|
- * We may need to check the cached dentry for staleness.
|
|
|
- */
|
|
|
- if (need_reval_dot(nd->path.dentry)) {
|
|
|
- if (nameidata_drop_rcu_last_maybe(nd))
|
|
|
- return -ECHILD;
|
|
|
- /* Note: we do not d_invalidate() */
|
|
|
- err = d_revalidate(nd->path.dentry, nd);
|
|
|
- if (!err)
|
|
|
- err = -ESTALE;
|
|
|
- if (err < 0)
|
|
|
- break;
|
|
|
- return 0;
|
|
|
- }
|
|
|
-return_base:
|
|
|
- if (nameidata_drop_rcu_last_maybe(nd))
|
|
|
- return -ECHILD;
|
|
|
+ nd->last_type = type;
|
|
|
return 0;
|
|
|
-out_dput:
|
|
|
- if (!(nd->flags & LOOKUP_RCU))
|
|
|
- path_put_conditional(&next, nd);
|
|
|
- break;
|
|
|
}
|
|
|
- if (!(nd->flags & LOOKUP_RCU))
|
|
|
- path_put(&nd->path);
|
|
|
-return_err:
|
|
|
+ terminate_walk(nd);
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
-static inline int path_walk_rcu(const char *name, struct nameidata *nd)
|
|
|
-{
|
|
|
- current->total_link_count = 0;
|
|
|
-
|
|
|
- return link_path_walk(name, nd);
|
|
|
-}
|
|
|
-
|
|
|
-static inline int path_walk_simple(const char *name, struct nameidata *nd)
|
|
|
-{
|
|
|
- current->total_link_count = 0;
|
|
|
-
|
|
|
- return link_path_walk(name, nd);
|
|
|
-}
|
|
|
-
|
|
|
-static int path_walk(const char *name, struct nameidata *nd)
|
|
|
-{
|
|
|
- struct path save = nd->path;
|
|
|
- int result;
|
|
|
-
|
|
|
- current->total_link_count = 0;
|
|
|
-
|
|
|
- /* make sure the stuff we saved doesn't go away */
|
|
|
- path_get(&save);
|
|
|
-
|
|
|
- result = link_path_walk(name, nd);
|
|
|
- if (result == -ESTALE) {
|
|
|
- /* nd->path had been dropped */
|
|
|
- current->total_link_count = 0;
|
|
|
- nd->path = save;
|
|
|
- nd->inode = save.dentry->d_inode;
|
|
|
- path_get(&nd->path);
|
|
|
- nd->flags |= LOOKUP_REVAL;
|
|
|
- result = link_path_walk(name, nd);
|
|
|
- }
|
|
|
-
|
|
|
- path_put(&save);
|
|
|
-
|
|
|
- return result;
|
|
|
-}
|
|
|
-
|
|
|
-static void path_finish_rcu(struct nameidata *nd)
|
|
|
-{
|
|
|
- if (nd->flags & LOOKUP_RCU) {
|
|
|
- /* RCU dangling. Cancel it. */
|
|
|
- nd->flags &= ~LOOKUP_RCU;
|
|
|
- nd->root.mnt = NULL;
|
|
|
- rcu_read_unlock();
|
|
|
- br_read_unlock(vfsmount_lock);
|
|
|
- }
|
|
|
- if (nd->file)
|
|
|
- fput(nd->file);
|
|
|
-}
|
|
|
-
|
|
|
-static int path_init_rcu(int dfd, const char *name, unsigned int flags, struct nameidata *nd)
|
|
|
+static int path_init(int dfd, const char *name, unsigned int flags,
|
|
|
+ struct nameidata *nd, struct file **fp)
|
|
|
{
|
|
|
int retval = 0;
|
|
|
int fput_needed;
|
|
|
struct file *file;
|
|
|
|
|
|
nd->last_type = LAST_ROOT; /* if there are only slashes... */
|
|
|
- nd->flags = flags | LOOKUP_RCU;
|
|
|
+ nd->flags = flags | LOOKUP_JUMPED;
|
|
|
nd->depth = 0;
|
|
|
+ if (flags & LOOKUP_ROOT) {
|
|
|
+ struct inode *inode = nd->root.dentry->d_inode;
|
|
|
+ if (*name) {
|
|
|
+ if (!inode->i_op->lookup)
|
|
|
+ return -ENOTDIR;
|
|
|
+ retval = inode_permission(inode, MAY_EXEC);
|
|
|
+ if (retval)
|
|
|
+ return retval;
|
|
|
+ }
|
|
|
+ nd->path = nd->root;
|
|
|
+ nd->inode = inode;
|
|
|
+ if (flags & LOOKUP_RCU) {
|
|
|
+ br_read_lock(vfsmount_lock);
|
|
|
+ rcu_read_lock();
|
|
|
+ nd->seq = __read_seqcount_begin(&nd->path.dentry->d_seq);
|
|
|
+ } else {
|
|
|
+ path_get(&nd->path);
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
nd->root.mnt = NULL;
|
|
|
- nd->file = NULL;
|
|
|
|
|
|
if (*name=='/') {
|
|
|
- struct fs_struct *fs = current->fs;
|
|
|
- unsigned seq;
|
|
|
-
|
|
|
- br_read_lock(vfsmount_lock);
|
|
|
- rcu_read_lock();
|
|
|
-
|
|
|
- do {
|
|
|
- seq = read_seqcount_begin(&fs->seq);
|
|
|
- nd->root = fs->root;
|
|
|
- nd->path = nd->root;
|
|
|
- nd->seq = __read_seqcount_begin(&nd->path.dentry->d_seq);
|
|
|
- } while (read_seqcount_retry(&fs->seq, seq));
|
|
|
-
|
|
|
+ if (flags & LOOKUP_RCU) {
|
|
|
+ br_read_lock(vfsmount_lock);
|
|
|
+ rcu_read_lock();
|
|
|
+ set_root_rcu(nd);
|
|
|
+ } else {
|
|
|
+ set_root(nd);
|
|
|
+ path_get(&nd->root);
|
|
|
+ }
|
|
|
+ nd->path = nd->root;
|
|
|
} else if (dfd == AT_FDCWD) {
|
|
|
- struct fs_struct *fs = current->fs;
|
|
|
- unsigned seq;
|
|
|
-
|
|
|
- br_read_lock(vfsmount_lock);
|
|
|
- rcu_read_lock();
|
|
|
+ if (flags & LOOKUP_RCU) {
|
|
|
+ struct fs_struct *fs = current->fs;
|
|
|
+ unsigned seq;
|
|
|
|
|
|
- do {
|
|
|
- seq = read_seqcount_begin(&fs->seq);
|
|
|
- nd->path = fs->pwd;
|
|
|
- nd->seq = __read_seqcount_begin(&nd->path.dentry->d_seq);
|
|
|
- } while (read_seqcount_retry(&fs->seq, seq));
|
|
|
+ br_read_lock(vfsmount_lock);
|
|
|
+ rcu_read_lock();
|
|
|
|
|
|
+ do {
|
|
|
+ seq = read_seqcount_begin(&fs->seq);
|
|
|
+ nd->path = fs->pwd;
|
|
|
+ nd->seq = __read_seqcount_begin(&nd->path.dentry->d_seq);
|
|
|
+ } while (read_seqcount_retry(&fs->seq, seq));
|
|
|
+ } else {
|
|
|
+ get_fs_pwd(current->fs, &nd->path);
|
|
|
+ }
|
|
|
} else {
|
|
|
struct dentry *dentry;
|
|
|
|
|
|
- file = fget_light(dfd, &fput_needed);
|
|
|
+ file = fget_raw_light(dfd, &fput_needed);
|
|
|
retval = -EBADF;
|
|
|
if (!file)
|
|
|
goto out_fail;
|
|
|
|
|
|
dentry = file->f_path.dentry;
|
|
|
|
|
|
- retval = -ENOTDIR;
|
|
|
- if (!S_ISDIR(dentry->d_inode->i_mode))
|
|
|
- goto fput_fail;
|
|
|
+ if (*name) {
|
|
|
+ retval = -ENOTDIR;
|
|
|
+ if (!S_ISDIR(dentry->d_inode->i_mode))
|
|
|
+ goto fput_fail;
|
|
|
|
|
|
- retval = file_permission(file, MAY_EXEC);
|
|
|
- if (retval)
|
|
|
- goto fput_fail;
|
|
|
+ retval = file_permission(file, MAY_EXEC);
|
|
|
+ if (retval)
|
|
|
+ goto fput_fail;
|
|
|
+ }
|
|
|
|
|
|
nd->path = file->f_path;
|
|
|
- if (fput_needed)
|
|
|
- nd->file = file;
|
|
|
-
|
|
|
- nd->seq = __read_seqcount_begin(&nd->path.dentry->d_seq);
|
|
|
- br_read_lock(vfsmount_lock);
|
|
|
- rcu_read_lock();
|
|
|
+ if (flags & LOOKUP_RCU) {
|
|
|
+ if (fput_needed)
|
|
|
+ *fp = file;
|
|
|
+ nd->seq = __read_seqcount_begin(&nd->path.dentry->d_seq);
|
|
|
+ br_read_lock(vfsmount_lock);
|
|
|
+ rcu_read_lock();
|
|
|
+ } else {
|
|
|
+ path_get(&file->f_path);
|
|
|
+ fput_light(file, fput_needed);
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
nd->inode = nd->path.dentry->d_inode;
|
|
|
return 0;
|
|
|
|
|
@@ -1644,60 +1586,23 @@ out_fail:
|
|
|
return retval;
|
|
|
}
|
|
|
|
|
|
-static int path_init(int dfd, const char *name, unsigned int flags, struct nameidata *nd)
|
|
|
+static inline int lookup_last(struct nameidata *nd, struct path *path)
|
|
|
{
|
|
|
- int retval = 0;
|
|
|
- int fput_needed;
|
|
|
- struct file *file;
|
|
|
-
|
|
|
- nd->last_type = LAST_ROOT; /* if there are only slashes... */
|
|
|
- nd->flags = flags;
|
|
|
- nd->depth = 0;
|
|
|
- nd->root.mnt = NULL;
|
|
|
-
|
|
|
- if (*name=='/') {
|
|
|
- set_root(nd);
|
|
|
- nd->path = nd->root;
|
|
|
- path_get(&nd->root);
|
|
|
- } else if (dfd == AT_FDCWD) {
|
|
|
- get_fs_pwd(current->fs, &nd->path);
|
|
|
- } else {
|
|
|
- struct dentry *dentry;
|
|
|
-
|
|
|
- file = fget_light(dfd, &fput_needed);
|
|
|
- retval = -EBADF;
|
|
|
- if (!file)
|
|
|
- goto out_fail;
|
|
|
-
|
|
|
- dentry = file->f_path.dentry;
|
|
|
-
|
|
|
- retval = -ENOTDIR;
|
|
|
- if (!S_ISDIR(dentry->d_inode->i_mode))
|
|
|
- goto fput_fail;
|
|
|
-
|
|
|
- retval = file_permission(file, MAY_EXEC);
|
|
|
- if (retval)
|
|
|
- goto fput_fail;
|
|
|
-
|
|
|
- nd->path = file->f_path;
|
|
|
- path_get(&file->f_path);
|
|
|
-
|
|
|
- fput_light(file, fput_needed);
|
|
|
- }
|
|
|
- nd->inode = nd->path.dentry->d_inode;
|
|
|
- return 0;
|
|
|
+ if (nd->last_type == LAST_NORM && nd->last.name[nd->last.len])
|
|
|
+ nd->flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
|
|
|
|
|
|
-fput_fail:
|
|
|
- fput_light(file, fput_needed);
|
|
|
-out_fail:
|
|
|
- return retval;
|
|
|
+ nd->flags &= ~LOOKUP_PARENT;
|
|
|
+ return walk_component(nd, path, &nd->last, nd->last_type,
|
|
|
+ nd->flags & LOOKUP_FOLLOW);
|
|
|
}
|
|
|
|
|
|
/* Returns 0 and nd will be valid on success; Retuns error, otherwise. */
|
|
|
-static int do_path_lookup(int dfd, const char *name,
|
|
|
+static int path_lookupat(int dfd, const char *name,
|
|
|
unsigned int flags, struct nameidata *nd)
|
|
|
{
|
|
|
- int retval;
|
|
|
+ struct file *base = NULL;
|
|
|
+ struct path path;
|
|
|
+ int err;
|
|
|
|
|
|
/*
|
|
|
* Path walking is largely split up into 2 different synchronisation
|
|
@@ -1713,44 +1618,75 @@ static int do_path_lookup(int dfd, const char *name,
|
|
|
* be handled by restarting a traditional ref-walk (which will always
|
|
|
* be able to complete).
|
|
|
*/
|
|
|
- retval = path_init_rcu(dfd, name, flags, nd);
|
|
|
- if (unlikely(retval))
|
|
|
- return retval;
|
|
|
- retval = path_walk_rcu(name, nd);
|
|
|
- path_finish_rcu(nd);
|
|
|
- if (nd->root.mnt) {
|
|
|
- path_put(&nd->root);
|
|
|
- nd->root.mnt = NULL;
|
|
|
+ err = path_init(dfd, name, flags | LOOKUP_PARENT, nd, &base);
|
|
|
+
|
|
|
+ if (unlikely(err))
|
|
|
+ return err;
|
|
|
+
|
|
|
+ current->total_link_count = 0;
|
|
|
+ err = link_path_walk(name, nd);
|
|
|
+
|
|
|
+ if (!err && !(flags & LOOKUP_PARENT)) {
|
|
|
+ err = lookup_last(nd, &path);
|
|
|
+ while (err > 0) {
|
|
|
+ void *cookie;
|
|
|
+ struct path link = path;
|
|
|
+ nd->flags |= LOOKUP_PARENT;
|
|
|
+ err = follow_link(&link, nd, &cookie);
|
|
|
+ if (!err)
|
|
|
+ err = lookup_last(nd, &path);
|
|
|
+ put_link(nd, &link, cookie);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- if (unlikely(retval == -ECHILD || retval == -ESTALE)) {
|
|
|
- /* slower, locked walk */
|
|
|
- if (retval == -ESTALE)
|
|
|
- flags |= LOOKUP_REVAL;
|
|
|
- retval = path_init(dfd, name, flags, nd);
|
|
|
- if (unlikely(retval))
|
|
|
- return retval;
|
|
|
- retval = path_walk(name, nd);
|
|
|
- if (nd->root.mnt) {
|
|
|
- path_put(&nd->root);
|
|
|
- nd->root.mnt = NULL;
|
|
|
+ if (nd->flags & LOOKUP_RCU) {
|
|
|
+ /* went all way through without dropping RCU */
|
|
|
+ BUG_ON(err);
|
|
|
+ if (nameidata_drop_rcu_last(nd))
|
|
|
+ err = -ECHILD;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!err)
|
|
|
+ err = handle_reval_path(nd);
|
|
|
+
|
|
|
+ if (!err && nd->flags & LOOKUP_DIRECTORY) {
|
|
|
+ if (!nd->inode->i_op->lookup) {
|
|
|
+ path_put(&nd->path);
|
|
|
+ return -ENOTDIR;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ if (base)
|
|
|
+ fput(base);
|
|
|
+
|
|
|
+ if (nd->root.mnt && !(nd->flags & LOOKUP_ROOT)) {
|
|
|
+ path_put(&nd->root);
|
|
|
+ nd->root.mnt = NULL;
|
|
|
+ }
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+static int do_path_lookup(int dfd, const char *name,
|
|
|
+ unsigned int flags, struct nameidata *nd)
|
|
|
+{
|
|
|
+ int retval = path_lookupat(dfd, name, flags | LOOKUP_RCU, nd);
|
|
|
+ if (unlikely(retval == -ECHILD))
|
|
|
+ retval = path_lookupat(dfd, name, flags, nd);
|
|
|
+ if (unlikely(retval == -ESTALE))
|
|
|
+ retval = path_lookupat(dfd, name, flags | LOOKUP_REVAL, nd);
|
|
|
+
|
|
|
if (likely(!retval)) {
|
|
|
if (unlikely(!audit_dummy_context())) {
|
|
|
if (nd->path.dentry && nd->inode)
|
|
|
audit_inode(name, nd->path.dentry);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
return retval;
|
|
|
}
|
|
|
|
|
|
-int path_lookup(const char *name, unsigned int flags,
|
|
|
- struct nameidata *nd)
|
|
|
+int kern_path_parent(const char *name, struct nameidata *nd)
|
|
|
{
|
|
|
- return do_path_lookup(AT_FDCWD, name, flags, nd);
|
|
|
+ return do_path_lookup(AT_FDCWD, name, LOOKUP_PARENT, nd);
|
|
|
}
|
|
|
|
|
|
int kern_path(const char *name, unsigned int flags, struct path *path)
|
|
@@ -1774,29 +1710,10 @@ int vfs_path_lookup(struct dentry *dentry, struct vfsmount *mnt,
|
|
|
const char *name, unsigned int flags,
|
|
|
struct nameidata *nd)
|
|
|
{
|
|
|
- int retval;
|
|
|
-
|
|
|
- /* same as do_path_lookup */
|
|
|
- nd->last_type = LAST_ROOT;
|
|
|
- nd->flags = flags;
|
|
|
- nd->depth = 0;
|
|
|
-
|
|
|
- nd->path.dentry = dentry;
|
|
|
- nd->path.mnt = mnt;
|
|
|
- path_get(&nd->path);
|
|
|
- nd->root = nd->path;
|
|
|
- path_get(&nd->root);
|
|
|
- nd->inode = nd->path.dentry->d_inode;
|
|
|
-
|
|
|
- retval = path_walk(name, nd);
|
|
|
- if (unlikely(!retval && !audit_dummy_context() && nd->path.dentry &&
|
|
|
- nd->inode))
|
|
|
- audit_inode(name, nd->path.dentry);
|
|
|
-
|
|
|
- path_put(&nd->root);
|
|
|
- nd->root.mnt = NULL;
|
|
|
-
|
|
|
- return retval;
|
|
|
+ nd->root.dentry = dentry;
|
|
|
+ nd->root.mnt = mnt;
|
|
|
+ /* the first argument of do_path_lookup() is ignored with LOOKUP_ROOT */
|
|
|
+ return do_path_lookup(AT_FDCWD, name, flags | LOOKUP_ROOT, nd);
|
|
|
}
|
|
|
|
|
|
static struct dentry *__lookup_hash(struct qstr *name,
|
|
@@ -1810,17 +1727,6 @@ static struct dentry *__lookup_hash(struct qstr *name,
|
|
|
if (err)
|
|
|
return ERR_PTR(err);
|
|
|
|
|
|
- /*
|
|
|
- * See if the low-level filesystem might want
|
|
|
- * to use its own hash..
|
|
|
- */
|
|
|
- if (base->d_flags & DCACHE_OP_HASH) {
|
|
|
- err = base->d_op->d_hash(base, inode, name);
|
|
|
- dentry = ERR_PTR(err);
|
|
|
- if (err < 0)
|
|
|
- goto out;
|
|
|
- }
|
|
|
-
|
|
|
/*
|
|
|
* Don't bother with __d_lookup: callers are for creat as
|
|
|
* well as unlink, so a lot of the time it would cost
|
|
@@ -1833,7 +1739,7 @@ static struct dentry *__lookup_hash(struct qstr *name,
|
|
|
|
|
|
if (!dentry)
|
|
|
dentry = d_alloc_and_lookup(base, name, nd);
|
|
|
-out:
|
|
|
+
|
|
|
return dentry;
|
|
|
}
|
|
|
|
|
@@ -1847,28 +1753,6 @@ static struct dentry *lookup_hash(struct nameidata *nd)
|
|
|
return __lookup_hash(&nd->last, nd->path.dentry, nd);
|
|
|
}
|
|
|
|
|
|
-static int __lookup_one_len(const char *name, struct qstr *this,
|
|
|
- struct dentry *base, int len)
|
|
|
-{
|
|
|
- unsigned long hash;
|
|
|
- unsigned int c;
|
|
|
-
|
|
|
- this->name = name;
|
|
|
- this->len = len;
|
|
|
- if (!len)
|
|
|
- return -EACCES;
|
|
|
-
|
|
|
- hash = init_name_hash();
|
|
|
- while (len--) {
|
|
|
- c = *(const unsigned char *)name++;
|
|
|
- if (c == '/' || c == '\0')
|
|
|
- return -EACCES;
|
|
|
- hash = partial_name_hash(c, hash);
|
|
|
- }
|
|
|
- this->hash = end_name_hash(hash);
|
|
|
- return 0;
|
|
|
-}
|
|
|
-
|
|
|
/**
|
|
|
* lookup_one_len - filesystem helper to lookup single pathname component
|
|
|
* @name: pathname component to lookup
|
|
@@ -1882,14 +1766,34 @@ static int __lookup_one_len(const char *name, struct qstr *this,
|
|
|
*/
|
|
|
struct dentry *lookup_one_len(const char *name, struct dentry *base, int len)
|
|
|
{
|
|
|
- int err;
|
|
|
struct qstr this;
|
|
|
+ unsigned long hash;
|
|
|
+ unsigned int c;
|
|
|
|
|
|
WARN_ON_ONCE(!mutex_is_locked(&base->d_inode->i_mutex));
|
|
|
|
|
|
- err = __lookup_one_len(name, &this, base, len);
|
|
|
- if (err)
|
|
|
- return ERR_PTR(err);
|
|
|
+ this.name = name;
|
|
|
+ this.len = len;
|
|
|
+ if (!len)
|
|
|
+ return ERR_PTR(-EACCES);
|
|
|
+
|
|
|
+ hash = init_name_hash();
|
|
|
+ while (len--) {
|
|
|
+ c = *(const unsigned char *)name++;
|
|
|
+ if (c == '/' || c == '\0')
|
|
|
+ return ERR_PTR(-EACCES);
|
|
|
+ hash = partial_name_hash(c, hash);
|
|
|
+ }
|
|
|
+ this.hash = end_name_hash(hash);
|
|
|
+ /*
|
|
|
+ * See if the low-level filesystem might want
|
|
|
+ * to use its own hash..
|
|
|
+ */
|
|
|
+ if (base->d_flags & DCACHE_OP_HASH) {
|
|
|
+ int err = base->d_op->d_hash(base, base->d_inode, &this);
|
|
|
+ if (err < 0)
|
|
|
+ return ERR_PTR(err);
|
|
|
+ }
|
|
|
|
|
|
return __lookup_hash(&this, base, NULL);
|
|
|
}
|
|
@@ -1898,7 +1802,7 @@ int user_path_at(int dfd, const char __user *name, unsigned flags,
|
|
|
struct path *path)
|
|
|
{
|
|
|
struct nameidata nd;
|
|
|
- char *tmp = getname(name);
|
|
|
+ char *tmp = getname_flags(name, flags);
|
|
|
int err = PTR_ERR(tmp);
|
|
|
if (!IS_ERR(tmp)) {
|
|
|
|
|
@@ -2078,12 +1982,16 @@ int vfs_create(struct inode *dir, struct dentry *dentry, int mode,
|
|
|
return error;
|
|
|
}
|
|
|
|
|
|
-int may_open(struct path *path, int acc_mode, int flag)
|
|
|
+static int may_open(struct path *path, int acc_mode, int flag)
|
|
|
{
|
|
|
struct dentry *dentry = path->dentry;
|
|
|
struct inode *inode = dentry->d_inode;
|
|
|
int error;
|
|
|
|
|
|
+ /* O_PATH? */
|
|
|
+ if (!acc_mode)
|
|
|
+ return 0;
|
|
|
+
|
|
|
if (!inode)
|
|
|
return -ENOENT;
|
|
|
|
|
@@ -2151,34 +2059,6 @@ static int handle_truncate(struct file *filp)
|
|
|
return error;
|
|
|
}
|
|
|
|
|
|
-/*
|
|
|
- * Be careful about ever adding any more callers of this
|
|
|
- * function. Its flags must be in the namei format, not
|
|
|
- * what get passed to sys_open().
|
|
|
- */
|
|
|
-static int __open_namei_create(struct nameidata *nd, struct path *path,
|
|
|
- int open_flag, int mode)
|
|
|
-{
|
|
|
- int error;
|
|
|
- struct dentry *dir = nd->path.dentry;
|
|
|
-
|
|
|
- if (!IS_POSIXACL(dir->d_inode))
|
|
|
- mode &= ~current_umask();
|
|
|
- error = security_path_mknod(&nd->path, path->dentry, mode, 0);
|
|
|
- if (error)
|
|
|
- goto out_unlock;
|
|
|
- error = vfs_create(dir->d_inode, path->dentry, mode, nd);
|
|
|
-out_unlock:
|
|
|
- mutex_unlock(&dir->d_inode->i_mutex);
|
|
|
- dput(nd->path.dentry);
|
|
|
- nd->path.dentry = path->dentry;
|
|
|
-
|
|
|
- if (error)
|
|
|
- return error;
|
|
|
- /* Don't check for write permission, don't truncate */
|
|
|
- return may_open(&nd->path, 0, open_flag & ~O_TRUNC);
|
|
|
-}
|
|
|
-
|
|
|
/*
|
|
|
* Note that while the flag value (low two bits) for sys_open means:
|
|
|
* 00 - read-only
|
|
@@ -2203,126 +2083,115 @@ static inline int open_to_namei_flags(int flag)
|
|
|
return flag;
|
|
|
}
|
|
|
|
|
|
-static int open_will_truncate(int flag, struct inode *inode)
|
|
|
-{
|
|
|
- /*
|
|
|
- * We'll never write to the fs underlying
|
|
|
- * a device file.
|
|
|
- */
|
|
|
- if (special_file(inode->i_mode))
|
|
|
- return 0;
|
|
|
- return (flag & O_TRUNC);
|
|
|
-}
|
|
|
-
|
|
|
-static struct file *finish_open(struct nameidata *nd,
|
|
|
- int open_flag, int acc_mode)
|
|
|
-{
|
|
|
- struct file *filp;
|
|
|
- int will_truncate;
|
|
|
- int error;
|
|
|
-
|
|
|
- will_truncate = open_will_truncate(open_flag, nd->path.dentry->d_inode);
|
|
|
- if (will_truncate) {
|
|
|
- error = mnt_want_write(nd->path.mnt);
|
|
|
- if (error)
|
|
|
- goto exit;
|
|
|
- }
|
|
|
- error = may_open(&nd->path, acc_mode, open_flag);
|
|
|
- if (error) {
|
|
|
- if (will_truncate)
|
|
|
- mnt_drop_write(nd->path.mnt);
|
|
|
- goto exit;
|
|
|
- }
|
|
|
- filp = nameidata_to_filp(nd);
|
|
|
- if (!IS_ERR(filp)) {
|
|
|
- error = ima_file_check(filp, acc_mode);
|
|
|
- if (error) {
|
|
|
- fput(filp);
|
|
|
- filp = ERR_PTR(error);
|
|
|
- }
|
|
|
- }
|
|
|
- if (!IS_ERR(filp)) {
|
|
|
- if (will_truncate) {
|
|
|
- error = handle_truncate(filp);
|
|
|
- if (error) {
|
|
|
- fput(filp);
|
|
|
- filp = ERR_PTR(error);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- /*
|
|
|
- * It is now safe to drop the mnt write
|
|
|
- * because the filp has had a write taken
|
|
|
- * on its behalf.
|
|
|
- */
|
|
|
- if (will_truncate)
|
|
|
- mnt_drop_write(nd->path.mnt);
|
|
|
- path_put(&nd->path);
|
|
|
- return filp;
|
|
|
-
|
|
|
-exit:
|
|
|
- path_put(&nd->path);
|
|
|
- return ERR_PTR(error);
|
|
|
-}
|
|
|
-
|
|
|
/*
|
|
|
- * Handle O_CREAT case for do_filp_open
|
|
|
+ * Handle the last step of open()
|
|
|
*/
|
|
|
static struct file *do_last(struct nameidata *nd, struct path *path,
|
|
|
- int open_flag, int acc_mode,
|
|
|
- int mode, const char *pathname)
|
|
|
+ const struct open_flags *op, const char *pathname)
|
|
|
{
|
|
|
struct dentry *dir = nd->path.dentry;
|
|
|
+ struct dentry *dentry;
|
|
|
+ int open_flag = op->open_flag;
|
|
|
+ int will_truncate = open_flag & O_TRUNC;
|
|
|
+ int want_write = 0;
|
|
|
+ int acc_mode = op->acc_mode;
|
|
|
struct file *filp;
|
|
|
- int error = -EISDIR;
|
|
|
+ int error;
|
|
|
+
|
|
|
+ nd->flags &= ~LOOKUP_PARENT;
|
|
|
+ nd->flags |= op->intent;
|
|
|
|
|
|
switch (nd->last_type) {
|
|
|
case LAST_DOTDOT:
|
|
|
- follow_dotdot(nd);
|
|
|
- dir = nd->path.dentry;
|
|
|
case LAST_DOT:
|
|
|
- if (need_reval_dot(dir)) {
|
|
|
- int status = d_revalidate(nd->path.dentry, nd);
|
|
|
- if (!status)
|
|
|
- status = -ESTALE;
|
|
|
- if (status < 0) {
|
|
|
- error = status;
|
|
|
- goto exit;
|
|
|
- }
|
|
|
- }
|
|
|
+ error = handle_dots(nd, nd->last_type);
|
|
|
+ if (error)
|
|
|
+ return ERR_PTR(error);
|
|
|
/* fallthrough */
|
|
|
case LAST_ROOT:
|
|
|
- goto exit;
|
|
|
+ if (nd->flags & LOOKUP_RCU) {
|
|
|
+ if (nameidata_drop_rcu_last(nd))
|
|
|
+ return ERR_PTR(-ECHILD);
|
|
|
+ }
|
|
|
+ error = handle_reval_path(nd);
|
|
|
+ if (error)
|
|
|
+ goto exit;
|
|
|
+ audit_inode(pathname, nd->path.dentry);
|
|
|
+ if (open_flag & O_CREAT) {
|
|
|
+ error = -EISDIR;
|
|
|
+ goto exit;
|
|
|
+ }
|
|
|
+ goto ok;
|
|
|
case LAST_BIND:
|
|
|
+ /* can't be RCU mode here */
|
|
|
+ error = handle_reval_path(nd);
|
|
|
+ if (error)
|
|
|
+ goto exit;
|
|
|
audit_inode(pathname, dir);
|
|
|
goto ok;
|
|
|
}
|
|
|
|
|
|
+ if (!(open_flag & O_CREAT)) {
|
|
|
+ int symlink_ok = 0;
|
|
|
+ if (nd->last.name[nd->last.len])
|
|
|
+ nd->flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
|
|
|
+ if (open_flag & O_PATH && !(nd->flags & LOOKUP_FOLLOW))
|
|
|
+ symlink_ok = 1;
|
|
|
+ /* we _can_ be in RCU mode here */
|
|
|
+ error = walk_component(nd, path, &nd->last, LAST_NORM,
|
|
|
+ !symlink_ok);
|
|
|
+ if (error < 0)
|
|
|
+ return ERR_PTR(error);
|
|
|
+ if (error) /* symlink */
|
|
|
+ return NULL;
|
|
|
+ /* sayonara */
|
|
|
+ if (nd->flags & LOOKUP_RCU) {
|
|
|
+ if (nameidata_drop_rcu_last(nd))
|
|
|
+ return ERR_PTR(-ECHILD);
|
|
|
+ }
|
|
|
+
|
|
|
+ error = -ENOTDIR;
|
|
|
+ if (nd->flags & LOOKUP_DIRECTORY) {
|
|
|
+ if (!nd->inode->i_op->lookup)
|
|
|
+ goto exit;
|
|
|
+ }
|
|
|
+ audit_inode(pathname, nd->path.dentry);
|
|
|
+ goto ok;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* create side of things */
|
|
|
+
|
|
|
+ if (nd->flags & LOOKUP_RCU) {
|
|
|
+ if (nameidata_drop_rcu_last(nd))
|
|
|
+ return ERR_PTR(-ECHILD);
|
|
|
+ }
|
|
|
+
|
|
|
+ audit_inode(pathname, dir);
|
|
|
+ error = -EISDIR;
|
|
|
/* trailing slashes? */
|
|
|
if (nd->last.name[nd->last.len])
|
|
|
goto exit;
|
|
|
|
|
|
mutex_lock(&dir->d_inode->i_mutex);
|
|
|
|
|
|
- path->dentry = lookup_hash(nd);
|
|
|
- path->mnt = nd->path.mnt;
|
|
|
-
|
|
|
- error = PTR_ERR(path->dentry);
|
|
|
- if (IS_ERR(path->dentry)) {
|
|
|
+ dentry = lookup_hash(nd);
|
|
|
+ error = PTR_ERR(dentry);
|
|
|
+ if (IS_ERR(dentry)) {
|
|
|
mutex_unlock(&dir->d_inode->i_mutex);
|
|
|
goto exit;
|
|
|
}
|
|
|
|
|
|
- if (IS_ERR(nd->intent.open.file)) {
|
|
|
- error = PTR_ERR(nd->intent.open.file);
|
|
|
- goto exit_mutex_unlock;
|
|
|
- }
|
|
|
+ path->dentry = dentry;
|
|
|
+ path->mnt = nd->path.mnt;
|
|
|
|
|
|
/* Negative dentry, just create the file */
|
|
|
- if (!path->dentry->d_inode) {
|
|
|
+ if (!dentry->d_inode) {
|
|
|
+ int mode = op->mode;
|
|
|
+ if (!IS_POSIXACL(dir->d_inode))
|
|
|
+ mode &= ~current_umask();
|
|
|
/*
|
|
|
* This write is needed to ensure that a
|
|
|
- * ro->rw transition does not occur between
|
|
|
+ * rw->ro transition does not occur between
|
|
|
* the time when the file is created and when
|
|
|
* a permanent write count is taken through
|
|
|
* the 'struct file' in nameidata_to_filp().
|
|
@@ -2330,22 +2199,21 @@ static struct file *do_last(struct nameidata *nd, struct path *path,
|
|
|
error = mnt_want_write(nd->path.mnt);
|
|
|
if (error)
|
|
|
goto exit_mutex_unlock;
|
|
|
- error = __open_namei_create(nd, path, open_flag, mode);
|
|
|
- if (error) {
|
|
|
- mnt_drop_write(nd->path.mnt);
|
|
|
- goto exit;
|
|
|
- }
|
|
|
- filp = nameidata_to_filp(nd);
|
|
|
- mnt_drop_write(nd->path.mnt);
|
|
|
- path_put(&nd->path);
|
|
|
- if (!IS_ERR(filp)) {
|
|
|
- error = ima_file_check(filp, acc_mode);
|
|
|
- if (error) {
|
|
|
- fput(filp);
|
|
|
- filp = ERR_PTR(error);
|
|
|
- }
|
|
|
- }
|
|
|
- return filp;
|
|
|
+ want_write = 1;
|
|
|
+ /* Don't check for write permission, don't truncate */
|
|
|
+ open_flag &= ~O_TRUNC;
|
|
|
+ will_truncate = 0;
|
|
|
+ acc_mode = MAY_OPEN;
|
|
|
+ error = security_path_mknod(&nd->path, dentry, mode, 0);
|
|
|
+ if (error)
|
|
|
+ goto exit_mutex_unlock;
|
|
|
+ error = vfs_create(dir->d_inode, dentry, mode, nd);
|
|
|
+ if (error)
|
|
|
+ goto exit_mutex_unlock;
|
|
|
+ mutex_unlock(&dir->d_inode->i_mutex);
|
|
|
+ dput(nd->path.dentry);
|
|
|
+ nd->path.dentry = dentry;
|
|
|
+ goto common;
|
|
|
}
|
|
|
|
|
|
/*
|
|
@@ -2375,7 +2243,40 @@ static struct file *do_last(struct nameidata *nd, struct path *path,
|
|
|
if (S_ISDIR(nd->inode->i_mode))
|
|
|
goto exit;
|
|
|
ok:
|
|
|
- filp = finish_open(nd, open_flag, acc_mode);
|
|
|
+ if (!S_ISREG(nd->inode->i_mode))
|
|
|
+ will_truncate = 0;
|
|
|
+
|
|
|
+ if (will_truncate) {
|
|
|
+ error = mnt_want_write(nd->path.mnt);
|
|
|
+ if (error)
|
|
|
+ goto exit;
|
|
|
+ want_write = 1;
|
|
|
+ }
|
|
|
+common:
|
|
|
+ error = may_open(&nd->path, acc_mode, open_flag);
|
|
|
+ if (error)
|
|
|
+ goto exit;
|
|
|
+ filp = nameidata_to_filp(nd);
|
|
|
+ if (!IS_ERR(filp)) {
|
|
|
+ error = ima_file_check(filp, op->acc_mode);
|
|
|
+ if (error) {
|
|
|
+ fput(filp);
|
|
|
+ filp = ERR_PTR(error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!IS_ERR(filp)) {
|
|
|
+ if (will_truncate) {
|
|
|
+ error = handle_truncate(filp);
|
|
|
+ if (error) {
|
|
|
+ fput(filp);
|
|
|
+ filp = ERR_PTR(error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+out:
|
|
|
+ if (want_write)
|
|
|
+ mnt_drop_write(nd->path.mnt);
|
|
|
+ path_put(&nd->path);
|
|
|
return filp;
|
|
|
|
|
|
exit_mutex_unlock:
|
|
@@ -2383,204 +2284,103 @@ exit_mutex_unlock:
|
|
|
exit_dput:
|
|
|
path_put_conditional(path, nd);
|
|
|
exit:
|
|
|
- path_put(&nd->path);
|
|
|
- return ERR_PTR(error);
|
|
|
+ filp = ERR_PTR(error);
|
|
|
+ goto out;
|
|
|
}
|
|
|
|
|
|
-/*
|
|
|
- * Note that the low bits of the passed in "open_flag"
|
|
|
- * are not the same as in the local variable "flag". See
|
|
|
- * open_to_namei_flags() for more details.
|
|
|
- */
|
|
|
-struct file *do_filp_open(int dfd, const char *pathname,
|
|
|
- int open_flag, int mode, int acc_mode)
|
|
|
+static struct file *path_openat(int dfd, const char *pathname,
|
|
|
+ struct nameidata *nd, const struct open_flags *op, int flags)
|
|
|
{
|
|
|
+ struct file *base = NULL;
|
|
|
struct file *filp;
|
|
|
- struct nameidata nd;
|
|
|
- int error;
|
|
|
struct path path;
|
|
|
- int count = 0;
|
|
|
- int flag = open_to_namei_flags(open_flag);
|
|
|
- int flags;
|
|
|
-
|
|
|
- if (!(open_flag & O_CREAT))
|
|
|
- mode = 0;
|
|
|
-
|
|
|
- /* Must never be set by userspace */
|
|
|
- open_flag &= ~FMODE_NONOTIFY;
|
|
|
-
|
|
|
- /*
|
|
|
- * O_SYNC is implemented as __O_SYNC|O_DSYNC. As many places only
|
|
|
- * check for O_DSYNC if the need any syncing at all we enforce it's
|
|
|
- * always set instead of having to deal with possibly weird behaviour
|
|
|
- * for malicious applications setting only __O_SYNC.
|
|
|
- */
|
|
|
- if (open_flag & __O_SYNC)
|
|
|
- open_flag |= O_DSYNC;
|
|
|
-
|
|
|
- if (!acc_mode)
|
|
|
- acc_mode = MAY_OPEN | ACC_MODE(open_flag);
|
|
|
-
|
|
|
- /* O_TRUNC implies we need access checks for write permissions */
|
|
|
- if (open_flag & O_TRUNC)
|
|
|
- acc_mode |= MAY_WRITE;
|
|
|
-
|
|
|
- /* Allow the LSM permission hook to distinguish append
|
|
|
- access from general write access. */
|
|
|
- if (open_flag & O_APPEND)
|
|
|
- acc_mode |= MAY_APPEND;
|
|
|
-
|
|
|
- flags = LOOKUP_OPEN;
|
|
|
- if (open_flag & O_CREAT) {
|
|
|
- flags |= LOOKUP_CREATE;
|
|
|
- if (open_flag & O_EXCL)
|
|
|
- flags |= LOOKUP_EXCL;
|
|
|
- }
|
|
|
- if (open_flag & O_DIRECTORY)
|
|
|
- flags |= LOOKUP_DIRECTORY;
|
|
|
- if (!(open_flag & O_NOFOLLOW))
|
|
|
- flags |= LOOKUP_FOLLOW;
|
|
|
+ int error;
|
|
|
|
|
|
filp = get_empty_filp();
|
|
|
if (!filp)
|
|
|
return ERR_PTR(-ENFILE);
|
|
|
|
|
|
- filp->f_flags = open_flag;
|
|
|
- nd.intent.open.file = filp;
|
|
|
- nd.intent.open.flags = flag;
|
|
|
- nd.intent.open.create_mode = mode;
|
|
|
-
|
|
|
- if (open_flag & O_CREAT)
|
|
|
- goto creat;
|
|
|
+ filp->f_flags = op->open_flag;
|
|
|
+ nd->intent.open.file = filp;
|
|
|
+ nd->intent.open.flags = open_to_namei_flags(op->open_flag);
|
|
|
+ nd->intent.open.create_mode = op->mode;
|
|
|
|
|
|
- /* !O_CREAT, simple open */
|
|
|
- error = do_path_lookup(dfd, pathname, flags, &nd);
|
|
|
+ error = path_init(dfd, pathname, flags | LOOKUP_PARENT, nd, &base);
|
|
|
if (unlikely(error))
|
|
|
- goto out_filp2;
|
|
|
- error = -ELOOP;
|
|
|
- if (!(nd.flags & LOOKUP_FOLLOW)) {
|
|
|
- if (nd.inode->i_op->follow_link)
|
|
|
- goto out_path2;
|
|
|
- }
|
|
|
- error = -ENOTDIR;
|
|
|
- if (nd.flags & LOOKUP_DIRECTORY) {
|
|
|
- if (!nd.inode->i_op->lookup)
|
|
|
- goto out_path2;
|
|
|
- }
|
|
|
- audit_inode(pathname, nd.path.dentry);
|
|
|
- filp = finish_open(&nd, open_flag, acc_mode);
|
|
|
-out2:
|
|
|
- release_open_intent(&nd);
|
|
|
- return filp;
|
|
|
-
|
|
|
-out_path2:
|
|
|
- path_put(&nd.path);
|
|
|
-out_filp2:
|
|
|
- filp = ERR_PTR(error);
|
|
|
- goto out2;
|
|
|
-
|
|
|
-creat:
|
|
|
- /* OK, have to create the file. Find the parent. */
|
|
|
- error = path_init_rcu(dfd, pathname,
|
|
|
- LOOKUP_PARENT | (flags & LOOKUP_REVAL), &nd);
|
|
|
- if (error)
|
|
|
goto out_filp;
|
|
|
- error = path_walk_rcu(pathname, &nd);
|
|
|
- path_finish_rcu(&nd);
|
|
|
- if (unlikely(error == -ECHILD || error == -ESTALE)) {
|
|
|
- /* slower, locked walk */
|
|
|
- if (error == -ESTALE) {
|
|
|
-reval:
|
|
|
- flags |= LOOKUP_REVAL;
|
|
|
- }
|
|
|
- error = path_init(dfd, pathname,
|
|
|
- LOOKUP_PARENT | (flags & LOOKUP_REVAL), &nd);
|
|
|
- if (error)
|
|
|
- goto out_filp;
|
|
|
|
|
|
- error = path_walk_simple(pathname, &nd);
|
|
|
- }
|
|
|
+ current->total_link_count = 0;
|
|
|
+ error = link_path_walk(pathname, nd);
|
|
|
if (unlikely(error))
|
|
|
goto out_filp;
|
|
|
- if (unlikely(!audit_dummy_context()))
|
|
|
- audit_inode(pathname, nd.path.dentry);
|
|
|
|
|
|
- /*
|
|
|
- * We have the parent and last component.
|
|
|
- */
|
|
|
- nd.flags = flags;
|
|
|
- filp = do_last(&nd, &path, open_flag, acc_mode, mode, pathname);
|
|
|
+ filp = do_last(nd, &path, op, pathname);
|
|
|
while (unlikely(!filp)) { /* trailing symlink */
|
|
|
struct path link = path;
|
|
|
- struct inode *linki = link.dentry->d_inode;
|
|
|
void *cookie;
|
|
|
- error = -ELOOP;
|
|
|
- if (!(nd.flags & LOOKUP_FOLLOW))
|
|
|
- goto exit_dput;
|
|
|
- if (count++ == 32)
|
|
|
- goto exit_dput;
|
|
|
- /*
|
|
|
- * This is subtle. Instead of calling do_follow_link() we do
|
|
|
- * the thing by hands. The reason is that this way we have zero
|
|
|
- * link_count and path_walk() (called from ->follow_link)
|
|
|
- * honoring LOOKUP_PARENT. After that we have the parent and
|
|
|
- * last component, i.e. we are in the same situation as after
|
|
|
- * the first path_walk(). Well, almost - if the last component
|
|
|
- * is normal we get its copy stored in nd->last.name and we will
|
|
|
- * have to putname() it when we are done. Procfs-like symlinks
|
|
|
- * just set LAST_BIND.
|
|
|
- */
|
|
|
- nd.flags |= LOOKUP_PARENT;
|
|
|
- error = security_inode_follow_link(link.dentry, &nd);
|
|
|
- if (error)
|
|
|
- goto exit_dput;
|
|
|
- error = __do_follow_link(&link, &nd, &cookie);
|
|
|
- if (unlikely(error)) {
|
|
|
- if (!IS_ERR(cookie) && linki->i_op->put_link)
|
|
|
- linki->i_op->put_link(link.dentry, &nd, cookie);
|
|
|
- /* nd.path had been dropped */
|
|
|
- nd.path = link;
|
|
|
- goto out_path;
|
|
|
+ if (!(nd->flags & LOOKUP_FOLLOW)) {
|
|
|
+ path_put_conditional(&path, nd);
|
|
|
+ path_put(&nd->path);
|
|
|
+ filp = ERR_PTR(-ELOOP);
|
|
|
+ break;
|
|
|
}
|
|
|
- nd.flags &= ~LOOKUP_PARENT;
|
|
|
- filp = do_last(&nd, &path, open_flag, acc_mode, mode, pathname);
|
|
|
- if (linki->i_op->put_link)
|
|
|
- linki->i_op->put_link(link.dentry, &nd, cookie);
|
|
|
- path_put(&link);
|
|
|
+ nd->flags |= LOOKUP_PARENT;
|
|
|
+ nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);
|
|
|
+ error = follow_link(&link, nd, &cookie);
|
|
|
+ if (unlikely(error))
|
|
|
+ filp = ERR_PTR(error);
|
|
|
+ else
|
|
|
+ filp = do_last(nd, &path, op, pathname);
|
|
|
+ put_link(nd, &link, cookie);
|
|
|
}
|
|
|
out:
|
|
|
- if (nd.root.mnt)
|
|
|
- path_put(&nd.root);
|
|
|
- if (filp == ERR_PTR(-ESTALE) && !(flags & LOOKUP_REVAL))
|
|
|
- goto reval;
|
|
|
- release_open_intent(&nd);
|
|
|
+ if (nd->root.mnt && !(nd->flags & LOOKUP_ROOT))
|
|
|
+ path_put(&nd->root);
|
|
|
+ if (base)
|
|
|
+ fput(base);
|
|
|
+ release_open_intent(nd);
|
|
|
return filp;
|
|
|
|
|
|
-exit_dput:
|
|
|
- path_put_conditional(&path, &nd);
|
|
|
-out_path:
|
|
|
- path_put(&nd.path);
|
|
|
out_filp:
|
|
|
filp = ERR_PTR(error);
|
|
|
goto out;
|
|
|
}
|
|
|
|
|
|
-/**
|
|
|
- * filp_open - open file and return file pointer
|
|
|
- *
|
|
|
- * @filename: path to open
|
|
|
- * @flags: open flags as per the open(2) second argument
|
|
|
- * @mode: mode for the new file if O_CREAT is set, else ignored
|
|
|
- *
|
|
|
- * This is the helper to open a file from kernelspace if you really
|
|
|
- * have to. But in generally you should not do this, so please move
|
|
|
- * along, nothing to see here..
|
|
|
- */
|
|
|
-struct file *filp_open(const char *filename, int flags, int mode)
|
|
|
+struct file *do_filp_open(int dfd, const char *pathname,
|
|
|
+ const struct open_flags *op, int flags)
|
|
|
{
|
|
|
- return do_filp_open(AT_FDCWD, filename, flags, mode, 0);
|
|
|
+ struct nameidata nd;
|
|
|
+ struct file *filp;
|
|
|
+
|
|
|
+ filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_RCU);
|
|
|
+ if (unlikely(filp == ERR_PTR(-ECHILD)))
|
|
|
+ filp = path_openat(dfd, pathname, &nd, op, flags);
|
|
|
+ if (unlikely(filp == ERR_PTR(-ESTALE)))
|
|
|
+ filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_REVAL);
|
|
|
+ return filp;
|
|
|
+}
|
|
|
+
|
|
|
+struct file *do_file_open_root(struct dentry *dentry, struct vfsmount *mnt,
|
|
|
+ const char *name, const struct open_flags *op, int flags)
|
|
|
+{
|
|
|
+ struct nameidata nd;
|
|
|
+ struct file *file;
|
|
|
+
|
|
|
+ nd.root.mnt = mnt;
|
|
|
+ nd.root.dentry = dentry;
|
|
|
+
|
|
|
+ flags |= LOOKUP_ROOT;
|
|
|
+
|
|
|
+ if (dentry->d_inode->i_op->follow_link && op->intent & LOOKUP_OPEN)
|
|
|
+ return ERR_PTR(-ELOOP);
|
|
|
+
|
|
|
+ file = path_openat(-1, name, &nd, op, flags | LOOKUP_RCU);
|
|
|
+ if (unlikely(file == ERR_PTR(-ECHILD)))
|
|
|
+ file = path_openat(-1, name, &nd, op, flags);
|
|
|
+ if (unlikely(file == ERR_PTR(-ESTALE)))
|
|
|
+ file = path_openat(-1, name, &nd, op, flags | LOOKUP_REVAL);
|
|
|
+ return file;
|
|
|
}
|
|
|
-EXPORT_SYMBOL(filp_open);
|
|
|
|
|
|
/**
|
|
|
* lookup_create - lookup a dentry, creating it if it doesn't exist
|
|
@@ -3119,7 +2919,11 @@ int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_de
|
|
|
return error;
|
|
|
|
|
|
mutex_lock(&inode->i_mutex);
|
|
|
- error = dir->i_op->link(old_dentry, dir, new_dentry);
|
|
|
+ /* Make sure we don't allow creating hardlink to an unlinked file */
|
|
|
+ if (inode->i_nlink == 0)
|
|
|
+ error = -ENOENT;
|
|
|
+ else
|
|
|
+ error = dir->i_op->link(old_dentry, dir, new_dentry);
|
|
|
mutex_unlock(&inode->i_mutex);
|
|
|
if (!error)
|
|
|
fsnotify_link(dir, inode, new_dentry);
|
|
@@ -3141,15 +2945,27 @@ SYSCALL_DEFINE5(linkat, int, olddfd, const char __user *, oldname,
|
|
|
struct dentry *new_dentry;
|
|
|
struct nameidata nd;
|
|
|
struct path old_path;
|
|
|
+ int how = 0;
|
|
|
int error;
|
|
|
char *to;
|
|
|
|
|
|
- if ((flags & ~AT_SYMLINK_FOLLOW) != 0)
|
|
|
+ if ((flags & ~(AT_SYMLINK_FOLLOW | AT_EMPTY_PATH)) != 0)
|
|
|
return -EINVAL;
|
|
|
+ /*
|
|
|
+ * To use null names we require CAP_DAC_READ_SEARCH
|
|
|
+ * This ensures that not everyone will be able to create
|
|
|
+ * handlink using the passed filedescriptor.
|
|
|
+ */
|
|
|
+ if (flags & AT_EMPTY_PATH) {
|
|
|
+ if (!capable(CAP_DAC_READ_SEARCH))
|
|
|
+ return -ENOENT;
|
|
|
+ how = LOOKUP_EMPTY;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (flags & AT_SYMLINK_FOLLOW)
|
|
|
+ how |= LOOKUP_FOLLOW;
|
|
|
|
|
|
- error = user_path_at(olddfd, oldname,
|
|
|
- flags & AT_SYMLINK_FOLLOW ? LOOKUP_FOLLOW : 0,
|
|
|
- &old_path);
|
|
|
+ error = user_path_at(olddfd, oldname, how, &old_path);
|
|
|
if (error)
|
|
|
return error;
|
|
|
|
|
@@ -3586,7 +3402,7 @@ EXPORT_SYMBOL(page_readlink);
|
|
|
EXPORT_SYMBOL(__page_symlink);
|
|
|
EXPORT_SYMBOL(page_symlink);
|
|
|
EXPORT_SYMBOL(page_symlink_inode_operations);
|
|
|
-EXPORT_SYMBOL(path_lookup);
|
|
|
+EXPORT_SYMBOL(kern_path_parent);
|
|
|
EXPORT_SYMBOL(kern_path);
|
|
|
EXPORT_SYMBOL(vfs_path_lookup);
|
|
|
EXPORT_SYMBOL(inode_permission);
|