Allow O_PATH for symlinks
authorAl Viro <viro@zeniv.linux.org.uk>
Sun, 13 Mar 2011 20:42:14 +0000 (16:42 -0400)
committerAl Viro <viro@zeniv.linux.org.uk>
Tue, 15 Mar 2011 06:21:45 +0000 (02:21 -0400)
At that point we can't do almost nothing with them.  They can be opened
with O_PATH, we can manipulate such descriptors with dup(), etc. and
we can see them in /proc/*/{fd,fdinfo}/*.

We can't (and won't be able to) follow /proc/*/fd/* symlinks for those;
there's simply not enough information for pathname resolution to go on
from such point - to resolve a symlink we need to know which directory
does it live in.

We will be able to do useful things with them after the next commit, though -
readlinkat() and fchownat() will be possible to use with dfd being an
O_PATH-opened symlink and empty relative pathname.  Combined with
open_by_handle() it'll give us a way to do realink-by-handle and
lchown-by-handle without messing with more redundant syscalls.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
fs/namei.c

index e1d9f90..9d4f327 100644 (file)
@@ -766,8 +766,14 @@ __do_follow_link(const struct path *link, struct nameidata *nd, void **p)
                error = 0;
                if (s)
                        error = __vfs_follow_link(nd, s);
-               else if (nd->last_type == LAST_BIND)
+               else if (nd->last_type == LAST_BIND) {
                        nd->flags |= LOOKUP_JUMPED;
+                       if (nd->path.dentry->d_inode->i_op->follow_link) {
+                               /* stepped on a _really_ weird one */
+                               path_put(&nd->path);
+                               error = -ELOOP;
+                       }
+               }
        }
        return error;
 }
@@ -1954,6 +1960,10 @@ static int may_open(struct path *path, int acc_mode, int flag)
        struct inode *inode = dentry->d_inode;
        int error;
 
+       /* O_PATH? */
+       if (!acc_mode)
+               return 0;
+
        if (!inode)
                return -ENOENT;
 
@@ -2056,7 +2066,7 @@ static struct file *do_last(struct nameidata *nd, struct path *path,
        int open_flag = op->open_flag;
        int will_truncate = open_flag & O_TRUNC;
        int want_write = 0;
-       int skip_perm = 0;
+       int acc_mode = op->acc_mode;
        struct file *filp;
        struct inode *inode;
        int error;
@@ -2095,8 +2105,11 @@ static struct file *do_last(struct nameidata *nd, struct path *path,
        }
 
        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 = do_lookup(nd, &nd->last, path, &inode);
                if (error) {
@@ -2108,7 +2121,7 @@ static struct file *do_last(struct nameidata *nd, struct path *path,
                        terminate_walk(nd);
                        return ERR_PTR(-ENOENT);
                }
-               if (unlikely(inode->i_op->follow_link)) {
+               if (unlikely(inode->i_op->follow_link && !symlink_ok)) {
                        /* We drop rcu-walk here */
                        if (nameidata_dentry_drop_rcu_maybe(nd, path->dentry))
                                return ERR_PTR(-ECHILD);
@@ -2175,7 +2188,7 @@ static struct file *do_last(struct nameidata *nd, struct path *path,
                /* Don't check for write permission, don't truncate */
                open_flag &= ~O_TRUNC;
                will_truncate = 0;
-               skip_perm = 1;
+               acc_mode = MAY_OPEN;
                error = security_path_mknod(&nd->path, dentry, mode, 0);
                if (error)
                        goto exit_mutex_unlock;
@@ -2225,7 +2238,7 @@ ok:
                want_write = 1;
        }
 common:
-       error = may_open(&nd->path, skip_perm ? 0 : op->acc_mode, open_flag);
+       error = may_open(&nd->path, acc_mode, open_flag);
        if (error)
                goto exit;
        filp = nameidata_to_filp(nd);
@@ -2358,7 +2371,7 @@ struct file *do_file_open_root(struct dentry *dentry, struct vfsmount *mnt,
 
        flags |= LOOKUP_ROOT;
 
-       if (dentry->d_inode->i_op->follow_link)
+       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);