Skip to content

Commit

Permalink
locks: break delegations on any attribute modification
Browse files Browse the repository at this point in the history
NFSv4 uses leases to guarantee that clients can cache metadata as well
as data.

Cc: Mikulas Patocka <mikulas@artax.karlin.mff.cuni.cz>
Cc: David Howells <dhowells@redhat.com>
Cc: Tyler Hicks <tyhicks@canonical.com>
Cc: Dustin Kirkland <dustin.kirkland@gazzang.com>
Acked-by: Jeff Layton <jlayton@redhat.com>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
  • Loading branch information
J. Bruce Fields authored and Al Viro committed Nov 9, 2013
1 parent 146a859 commit 27ac0ff
Show file tree
Hide file tree
Showing 10 changed files with 69 additions and 17 deletions.
4 changes: 2 additions & 2 deletions drivers/base/devtmpfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ static int handle_create(const char *nodename, umode_t mode, kuid_t uid,
newattrs.ia_gid = gid;
newattrs.ia_valid = ATTR_MODE|ATTR_UID|ATTR_GID;
mutex_lock(&dentry->d_inode->i_mutex);
notify_change(dentry, &newattrs);
notify_change(dentry, &newattrs, NULL);
mutex_unlock(&dentry->d_inode->i_mutex);

/* mark as kernel-created inode */
Expand Down Expand Up @@ -322,7 +322,7 @@ static int handle_remove(const char *nodename, struct device *dev)
newattrs.ia_valid =
ATTR_UID|ATTR_GID|ATTR_MODE;
mutex_lock(&dentry->d_inode->i_mutex);
notify_change(dentry, &newattrs);
notify_change(dentry, &newattrs, NULL);
mutex_unlock(&dentry->d_inode->i_mutex);
err = vfs_unlink(parent.dentry->d_inode, dentry, NULL);
if (!err || err == -ENOENT)
Expand Down
25 changes: 24 additions & 1 deletion fs/attr.c
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,27 @@ void setattr_copy(struct inode *inode, const struct iattr *attr)
}
EXPORT_SYMBOL(setattr_copy);

int notify_change(struct dentry * dentry, struct iattr * attr)
/**
* notify_change - modify attributes of a filesytem object
* @dentry: object affected
* @iattr: new attributes
* @delegated_inode: returns inode, if the inode is delegated
*
* The caller must hold the i_mutex on the affected object.
*
* If notify_change discovers a delegation in need of breaking,
* it will return -EWOULDBLOCK and return a reference to the inode in
* delegated_inode. The caller should then break the delegation and
* retry. Because breaking a delegation may take a long time, the
* caller should drop the i_mutex before doing so.
*
* Alternatively, a caller may pass NULL for delegated_inode. This may
* be appropriate for callers that expect the underlying filesystem not
* to be NFS exported. Also, passing NULL is fine for callers holding
* the file open for write, as there can be no conflicting delegation in
* that case.
*/
int notify_change(struct dentry * dentry, struct iattr * attr, struct inode **delegated_inode)
{
struct inode *inode = dentry->d_inode;
umode_t mode = inode->i_mode;
Expand Down Expand Up @@ -241,6 +261,9 @@ int notify_change(struct dentry * dentry, struct iattr * attr)
return 0;

error = security_inode_setattr(dentry, attr);
if (error)
return error;
error = try_break_deleg(inode, delegated_inode);
if (error)
return error;

Expand Down
4 changes: 2 additions & 2 deletions fs/cachefiles/interface.c
Original file line number Diff line number Diff line change
Expand Up @@ -449,14 +449,14 @@ static int cachefiles_attr_changed(struct fscache_object *_object)
_debug("discard tail %llx", oi_size);
newattrs.ia_valid = ATTR_SIZE;
newattrs.ia_size = oi_size & PAGE_MASK;
ret = notify_change(object->backer, &newattrs);
ret = notify_change(object->backer, &newattrs, NULL);
if (ret < 0)
goto truncate_failed;
}

newattrs.ia_valid = ATTR_SIZE;
newattrs.ia_size = ni_size;
ret = notify_change(object->backer, &newattrs);
ret = notify_change(object->backer, &newattrs, NULL);

truncate_failed:
mutex_unlock(&object->backer->d_inode->i_mutex);
Expand Down
4 changes: 2 additions & 2 deletions fs/ecryptfs/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -882,7 +882,7 @@ int ecryptfs_truncate(struct dentry *dentry, loff_t new_length)
struct dentry *lower_dentry = ecryptfs_dentry_to_lower(dentry);

mutex_lock(&lower_dentry->d_inode->i_mutex);
rc = notify_change(lower_dentry, &lower_ia);
rc = notify_change(lower_dentry, &lower_ia, NULL);
mutex_unlock(&lower_dentry->d_inode->i_mutex);
}
return rc;
Expand Down Expand Up @@ -983,7 +983,7 @@ static int ecryptfs_setattr(struct dentry *dentry, struct iattr *ia)
lower_ia.ia_valid &= ~ATTR_MODE;

mutex_lock(&lower_dentry->d_inode->i_mutex);
rc = notify_change(lower_dentry, &lower_ia);
rc = notify_change(lower_dentry, &lower_ia, NULL);
mutex_unlock(&lower_dentry->d_inode->i_mutex);
out:
fsstack_copy_attr_all(inode, lower_inode);
Expand Down
2 changes: 1 addition & 1 deletion fs/hpfs/namei.c
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ static int hpfs_unlink(struct inode *dir, struct dentry *dentry)
/*printk("HPFS: truncating file before delete.\n");*/
newattrs.ia_size = 0;
newattrs.ia_valid = ATTR_SIZE | ATTR_CTIME;
err = notify_change(dentry, &newattrs);
err = notify_change(dentry, &newattrs, NULL);
put_write_access(inode);
if (!err)
goto again;
Expand Down
6 changes: 5 additions & 1 deletion fs/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -1603,7 +1603,11 @@ static int __remove_suid(struct dentry *dentry, int kill)
struct iattr newattrs;

newattrs.ia_valid = ATTR_FORCE | kill;
return notify_change(dentry, &newattrs);
/*
* Note we call this on write, so notify_change will not
* encounter any conflicting delegations:
*/
return notify_change(dentry, &newattrs, NULL);
}

int file_remove_suid(struct file *file)
Expand Down
8 changes: 6 additions & 2 deletions fs/nfsd/vfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap,
goto out_nfserr;
fh_lock(fhp);

host_err = notify_change(dentry, iap);
host_err = notify_change(dentry, iap, NULL);
err = nfserrno(host_err);
fh_unlock(fhp);
}
Expand Down Expand Up @@ -988,7 +988,11 @@ static void kill_suid(struct dentry *dentry)
ia.ia_valid = ATTR_KILL_SUID | ATTR_KILL_SGID | ATTR_KILL_PRIV;

mutex_lock(&dentry->d_inode->i_mutex);
notify_change(dentry, &ia);
/*
* Note we call this on write, so notify_change will not
* encounter any conflicting delegations:
*/
notify_change(dentry, &ia, NULL);
mutex_unlock(&dentry->d_inode->i_mutex);
}

Expand Down
22 changes: 18 additions & 4 deletions fs/open.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ int do_truncate(struct dentry *dentry, loff_t length, unsigned int time_attrs,
newattrs.ia_valid |= ret | ATTR_FORCE;

mutex_lock(&dentry->d_inode->i_mutex);
ret = notify_change(dentry, &newattrs);
/* Note any delegations or leases have already been broken: */
ret = notify_change(dentry, &newattrs, NULL);
mutex_unlock(&dentry->d_inode->i_mutex);
return ret;
}
Expand Down Expand Up @@ -464,21 +465,28 @@ SYSCALL_DEFINE1(chroot, const char __user *, filename)
static int chmod_common(struct path *path, umode_t mode)
{
struct inode *inode = path->dentry->d_inode;
struct inode *delegated_inode = NULL;
struct iattr newattrs;
int error;

error = mnt_want_write(path->mnt);
if (error)
return error;
retry_deleg:
mutex_lock(&inode->i_mutex);
error = security_path_chmod(path, mode);
if (error)
goto out_unlock;
newattrs.ia_mode = (mode & S_IALLUGO) | (inode->i_mode & ~S_IALLUGO);
newattrs.ia_valid = ATTR_MODE | ATTR_CTIME;
error = notify_change(path->dentry, &newattrs);
error = notify_change(path->dentry, &newattrs, &delegated_inode);
out_unlock:
mutex_unlock(&inode->i_mutex);
if (delegated_inode) {
error = break_deleg_wait(&delegated_inode);
if (!error)
goto retry_deleg;
}
mnt_drop_write(path->mnt);
return error;
}
Expand Down Expand Up @@ -522,6 +530,7 @@ SYSCALL_DEFINE2(chmod, const char __user *, filename, umode_t, mode)
static int chown_common(struct path *path, uid_t user, gid_t group)
{
struct inode *inode = path->dentry->d_inode;
struct inode *delegated_inode = NULL;
int error;
struct iattr newattrs;
kuid_t uid;
Expand All @@ -546,12 +555,17 @@ static int chown_common(struct path *path, uid_t user, gid_t group)
if (!S_ISDIR(inode->i_mode))
newattrs.ia_valid |=
ATTR_KILL_SUID | ATTR_KILL_SGID | ATTR_KILL_PRIV;
retry_deleg:
mutex_lock(&inode->i_mutex);
error = security_path_chown(path, uid, gid);
if (!error)
error = notify_change(path->dentry, &newattrs);
error = notify_change(path->dentry, &newattrs, &delegated_inode);
mutex_unlock(&inode->i_mutex);

if (delegated_inode) {
error = break_deleg_wait(&delegated_inode);
if (!error)
goto retry_deleg;
}
return error;
}

Expand Down
9 changes: 8 additions & 1 deletion fs/utimes.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ static int utimes_common(struct path *path, struct timespec *times)
int error;
struct iattr newattrs;
struct inode *inode = path->dentry->d_inode;
struct inode *delegated_inode = NULL;

error = mnt_want_write(path->mnt);
if (error)
Expand Down Expand Up @@ -101,9 +102,15 @@ static int utimes_common(struct path *path, struct timespec *times)
goto mnt_drop_write_and_out;
}
}
retry_deleg:
mutex_lock(&inode->i_mutex);
error = notify_change(path->dentry, &newattrs);
error = notify_change(path->dentry, &newattrs, &delegated_inode);
mutex_unlock(&inode->i_mutex);
if (delegated_inode) {
error = break_deleg_wait(&delegated_inode);
if (!error)
goto retry_deleg;
}

mnt_drop_write_and_out:
mnt_drop_write(path->mnt);
Expand Down
2 changes: 1 addition & 1 deletion include/linux/fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -2278,7 +2278,7 @@ extern void emergency_remount(void);
#ifdef CONFIG_BLOCK
extern sector_t bmap(struct inode *, sector_t);
#endif
extern int notify_change(struct dentry *, struct iattr *);
extern int notify_change(struct dentry *, struct iattr *, struct inode **);
extern int inode_permission(struct inode *, int);
extern int generic_permission(struct inode *, int);

Expand Down

0 comments on commit 27ac0ff

Please sign in to comment.