nfs: don't open in ->d_revalidate

NFSv4 can't do reliable opens in d_revalidate, since it cannot know whether a
mount needs to be followed or not.  It does check d_mountpoint() on the dentry,
which can result in a weird error if the VFS found that the mount does not in
fact need to be followed, e.g.:

  # mount --bind /mnt/nfs /mnt/nfs-clone
  # echo something > /mnt/nfs/tmp/bar
  # echo x > /tmp/file
  # mount --bind /tmp/file /mnt/nfs-clone/tmp/bar
  # cat  /mnt/nfs/tmp/bar
  cat: /mnt/nfs/tmp/bar: Not a directory

Which should, by any sane filesystem, result in "something" being printed.

So instead do the open in f_op->open() and in the unlikely case that the cached
dentry turned out to be invalid, drop the dentry and return EOPENSTALE to let
the VFS retry.

Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
CC: Trond Myklebust <Trond.Myklebust@netapp.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
This commit is contained in:
Miklos Szeredi 2012-05-21 17:30:20 +02:00 коммит произвёл Al Viro
Родитель 16b1c1cd71
Коммит 0ef97dcfce
2 изменённых файлов: 78 добавлений и 55 удалений

Просмотреть файл

@ -1354,10 +1354,10 @@ out:
} }
#ifdef CONFIG_NFS_V4 #ifdef CONFIG_NFS_V4
static int nfs_open_revalidate(struct dentry *, struct nameidata *); static int nfs4_lookup_revalidate(struct dentry *, struct nameidata *);
const struct dentry_operations nfs4_dentry_operations = { const struct dentry_operations nfs4_dentry_operations = {
.d_revalidate = nfs_open_revalidate, .d_revalidate = nfs4_lookup_revalidate,
.d_delete = nfs_dentry_delete, .d_delete = nfs_dentry_delete,
.d_iput = nfs_dentry_iput, .d_iput = nfs_dentry_iput,
.d_automount = nfs_d_automount, .d_automount = nfs_d_automount,
@ -1519,13 +1519,11 @@ no_open:
return nfs_lookup(dir, dentry, nd); return nfs_lookup(dir, dentry, nd);
} }
static int nfs_open_revalidate(struct dentry *dentry, struct nameidata *nd) static int nfs4_lookup_revalidate(struct dentry *dentry, struct nameidata *nd)
{ {
struct dentry *parent = NULL; struct dentry *parent = NULL;
struct inode *inode; struct inode *inode;
struct inode *dir; struct inode *dir;
struct nfs_open_context *ctx;
struct iattr attr;
int openflags, ret = 0; int openflags, ret = 0;
if (nd->flags & LOOKUP_RCU) if (nd->flags & LOOKUP_RCU)
@ -1554,57 +1552,13 @@ static int nfs_open_revalidate(struct dentry *dentry, struct nameidata *nd)
/* We cannot do exclusive creation on a positive dentry */ /* We cannot do exclusive creation on a positive dentry */
if ((openflags & (O_CREAT|O_EXCL)) == (O_CREAT|O_EXCL)) if ((openflags & (O_CREAT|O_EXCL)) == (O_CREAT|O_EXCL))
goto no_open_dput; goto no_open_dput;
/* We can't create new files here */
openflags &= ~(O_CREAT|O_EXCL);
ctx = create_nfs_open_context(dentry, openflags); /* Let f_op->open() actually open (and revalidate) the file */
ret = PTR_ERR(ctx);
if (IS_ERR(ctx))
goto out;
attr.ia_valid = ATTR_OPEN;
if (openflags & O_TRUNC) {
attr.ia_valid |= ATTR_SIZE;
attr.ia_size = 0;
nfs_wb_all(inode);
}
/*
* Note: we're not holding inode->i_mutex and so may be racing with
* operations that change the directory. We therefore save the
* change attribute *before* we do the RPC call.
*/
inode = NFS_PROTO(dir)->open_context(dir, ctx, openflags, &attr);
if (IS_ERR(inode)) {
ret = PTR_ERR(inode);
switch (ret) {
case -EPERM:
case -EACCES:
case -EDQUOT:
case -ENOSPC:
case -EROFS:
goto out_put_ctx;
default:
goto out_drop;
}
}
iput(inode);
if (inode != dentry->d_inode)
goto out_drop;
nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
ret = nfs_intent_set_file(nd, ctx);
if (ret >= 0)
ret = 1; ret = 1;
out: out:
dput(parent); dput(parent);
return ret; return ret;
out_drop:
d_drop(dentry);
ret = 0;
out_put_ctx:
put_nfs_open_context(ctx);
goto out;
no_open_dput: no_open_dput:
dput(parent); dput(parent);

Просмотреть файл

@ -879,12 +879,81 @@ const struct file_operations nfs_file_operations = {
static int static int
nfs4_file_open(struct inode *inode, struct file *filp) nfs4_file_open(struct inode *inode, struct file *filp)
{ {
struct nfs_open_context *ctx;
struct dentry *dentry = filp->f_path.dentry;
struct dentry *parent = NULL;
struct inode *dir;
unsigned openflags = filp->f_flags;
struct iattr attr;
int err;
BUG_ON(inode != dentry->d_inode);
/* /*
* NFSv4 opens are handled in d_lookup and d_revalidate. If we get to * If no cached dentry exists or if it's negative, NFSv4 handled the
* this point, then something is very wrong * opens in ->lookup() or ->create().
*
* We only get this far for a cached positive dentry. We skipped
* revalidation, so handle it here by dropping the dentry and returning
* -EOPENSTALE. The VFS will retry the lookup/create/open.
*/ */
dprintk("NFS: %s called! inode=%p filp=%p\n", __func__, inode, filp);
return -ENOTDIR; dprintk("NFS: open file(%s/%s)\n",
dentry->d_parent->d_name.name,
dentry->d_name.name);
if ((openflags & O_ACCMODE) == 3)
openflags--;
/* We can't create new files here */
openflags &= ~(O_CREAT|O_EXCL);
parent = dget_parent(dentry);
dir = parent->d_inode;
ctx = alloc_nfs_open_context(filp->f_path.dentry, filp->f_mode);
err = PTR_ERR(ctx);
if (IS_ERR(ctx))
goto out;
attr.ia_valid = ATTR_OPEN;
if (openflags & O_TRUNC) {
attr.ia_valid |= ATTR_SIZE;
attr.ia_size = 0;
nfs_wb_all(inode);
}
inode = NFS_PROTO(dir)->open_context(dir, ctx, openflags, &attr);
if (IS_ERR(inode)) {
err = PTR_ERR(inode);
switch (err) {
case -EPERM:
case -EACCES:
case -EDQUOT:
case -ENOSPC:
case -EROFS:
goto out_put_ctx;
default:
goto out_drop;
}
}
iput(inode);
if (inode != dentry->d_inode)
goto out_drop;
nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
nfs_file_set_open_context(filp, ctx);
err = 0;
out_put_ctx:
put_nfs_open_context(ctx);
out:
dput(parent);
return err;
out_drop:
d_drop(dentry);
err = -EOPENSTALE;
goto out_put_ctx;
} }
const struct file_operations nfs4_file_operations = { const struct file_operations nfs4_file_operations = {