nfsd: split up nfsd_setattr
Split out two helpers to make the code more readable and easier to verify for correctness. Signed-off-by: Christoph Hellwig <hch@lst.de> Cc: stable@vger.kernel.org Signed-off-by: J. Bruce Fields <bfields@redhat.com>
This commit is contained in:
parent
2d3c627502
commit
818e5a22e9
1 changed files with 105 additions and 81 deletions
186
fs/nfsd/vfs.c
186
fs/nfsd/vfs.c
|
@ -298,8 +298,104 @@ commit_metadata(struct svc_fh *fhp)
|
|||
}
|
||||
|
||||
/*
|
||||
* Set various file attributes.
|
||||
* N.B. After this call fhp needs an fh_put
|
||||
* Go over the attributes and take care of the small differences between
|
||||
* NFS semantics and what Linux expects.
|
||||
*/
|
||||
static void
|
||||
nfsd_sanitize_attrs(struct inode *inode, struct iattr *iap)
|
||||
{
|
||||
/*
|
||||
* NFSv2 does not differentiate between "set-[ac]time-to-now"
|
||||
* which only requires access, and "set-[ac]time-to-X" which
|
||||
* requires ownership.
|
||||
* So if it looks like it might be "set both to the same time which
|
||||
* is close to now", and if inode_change_ok fails, then we
|
||||
* convert to "set to now" instead of "set to explicit time"
|
||||
*
|
||||
* We only call inode_change_ok as the last test as technically
|
||||
* it is not an interface that we should be using.
|
||||
*/
|
||||
#define BOTH_TIME_SET (ATTR_ATIME_SET | ATTR_MTIME_SET)
|
||||
#define MAX_TOUCH_TIME_ERROR (30*60)
|
||||
if ((iap->ia_valid & BOTH_TIME_SET) == BOTH_TIME_SET &&
|
||||
iap->ia_mtime.tv_sec == iap->ia_atime.tv_sec) {
|
||||
/*
|
||||
* Looks probable.
|
||||
*
|
||||
* Now just make sure time is in the right ballpark.
|
||||
* Solaris, at least, doesn't seem to care what the time
|
||||
* request is. We require it be within 30 minutes of now.
|
||||
*/
|
||||
time_t delta = iap->ia_atime.tv_sec - get_seconds();
|
||||
if (delta < 0)
|
||||
delta = -delta;
|
||||
if (delta < MAX_TOUCH_TIME_ERROR &&
|
||||
inode_change_ok(inode, iap) != 0) {
|
||||
/*
|
||||
* Turn off ATTR_[AM]TIME_SET but leave ATTR_[AM]TIME.
|
||||
* This will cause notify_change to set these times
|
||||
* to "now"
|
||||
*/
|
||||
iap->ia_valid &= ~BOTH_TIME_SET;
|
||||
}
|
||||
}
|
||||
|
||||
/* sanitize the mode change */
|
||||
if (iap->ia_valid & ATTR_MODE) {
|
||||
iap->ia_mode &= S_IALLUGO;
|
||||
iap->ia_mode |= (inode->i_mode & ~S_IALLUGO);
|
||||
}
|
||||
|
||||
/* Revoke setuid/setgid on chown */
|
||||
if (!S_ISDIR(inode->i_mode) &&
|
||||
(((iap->ia_valid & ATTR_UID) && !uid_eq(iap->ia_uid, inode->i_uid)) ||
|
||||
((iap->ia_valid & ATTR_GID) && !gid_eq(iap->ia_gid, inode->i_gid)))) {
|
||||
iap->ia_valid |= ATTR_KILL_PRIV;
|
||||
if (iap->ia_valid & ATTR_MODE) {
|
||||
/* we're setting mode too, just clear the s*id bits */
|
||||
iap->ia_mode &= ~S_ISUID;
|
||||
if (iap->ia_mode & S_IXGRP)
|
||||
iap->ia_mode &= ~S_ISGID;
|
||||
} else {
|
||||
/* set ATTR_KILL_* bits and let VFS handle it */
|
||||
iap->ia_valid |= (ATTR_KILL_SUID | ATTR_KILL_SGID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static __be32
|
||||
nfsd_get_write_access(struct svc_rqst *rqstp, struct svc_fh *fhp,
|
||||
struct iattr *iap)
|
||||
{
|
||||
struct inode *inode = fhp->fh_dentry->d_inode;
|
||||
int host_err;
|
||||
|
||||
if (iap->ia_size < inode->i_size) {
|
||||
__be32 err;
|
||||
|
||||
err = nfsd_permission(rqstp, fhp->fh_export, fhp->fh_dentry,
|
||||
NFSD_MAY_TRUNC | NFSD_MAY_OWNER_OVERRIDE);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
host_err = get_write_access(inode);
|
||||
if (host_err)
|
||||
goto out_nfserrno;
|
||||
|
||||
host_err = locks_verify_truncate(inode, NULL, iap->ia_size);
|
||||
if (host_err)
|
||||
goto out_put_write_access;
|
||||
return 0;
|
||||
|
||||
out_put_write_access:
|
||||
put_write_access(inode);
|
||||
out_nfserrno:
|
||||
return nfserrno(host_err);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set various file attributes. After this call fhp needs an fh_put.
|
||||
*/
|
||||
__be32
|
||||
nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap,
|
||||
|
@ -333,91 +429,19 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap,
|
|||
if (!iap->ia_valid)
|
||||
goto out;
|
||||
|
||||
nfsd_sanitize_attrs(inode, iap);
|
||||
|
||||
/*
|
||||
* NFSv2 does not differentiate between "set-[ac]time-to-now"
|
||||
* which only requires access, and "set-[ac]time-to-X" which
|
||||
* requires ownership.
|
||||
* So if it looks like it might be "set both to the same time which
|
||||
* is close to now", and if inode_change_ok fails, then we
|
||||
* convert to "set to now" instead of "set to explicit time"
|
||||
*
|
||||
* We only call inode_change_ok as the last test as technically
|
||||
* it is not an interface that we should be using. It is only
|
||||
* valid if the filesystem does not define it's own i_op->setattr.
|
||||
*/
|
||||
#define BOTH_TIME_SET (ATTR_ATIME_SET | ATTR_MTIME_SET)
|
||||
#define MAX_TOUCH_TIME_ERROR (30*60)
|
||||
if ((iap->ia_valid & BOTH_TIME_SET) == BOTH_TIME_SET &&
|
||||
iap->ia_mtime.tv_sec == iap->ia_atime.tv_sec) {
|
||||
/*
|
||||
* Looks probable.
|
||||
*
|
||||
* Now just make sure time is in the right ballpark.
|
||||
* Solaris, at least, doesn't seem to care what the time
|
||||
* request is. We require it be within 30 minutes of now.
|
||||
*/
|
||||
time_t delta = iap->ia_atime.tv_sec - get_seconds();
|
||||
if (delta < 0)
|
||||
delta = -delta;
|
||||
if (delta < MAX_TOUCH_TIME_ERROR &&
|
||||
inode_change_ok(inode, iap) != 0) {
|
||||
/*
|
||||
* Turn off ATTR_[AM]TIME_SET but leave ATTR_[AM]TIME.
|
||||
* This will cause notify_change to set these times
|
||||
* to "now"
|
||||
*/
|
||||
iap->ia_valid &= ~BOTH_TIME_SET;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The size case is special.
|
||||
* It changes the file as well as the attributes.
|
||||
* The size case is special, it changes the file in addition to the
|
||||
* attributes.
|
||||
*/
|
||||
if (iap->ia_valid & ATTR_SIZE) {
|
||||
if (iap->ia_size < inode->i_size) {
|
||||
err = nfsd_permission(rqstp, fhp->fh_export, dentry,
|
||||
NFSD_MAY_TRUNC|NFSD_MAY_OWNER_OVERRIDE);
|
||||
if (err)
|
||||
goto out;
|
||||
}
|
||||
|
||||
host_err = get_write_access(inode);
|
||||
if (host_err)
|
||||
goto out_nfserr;
|
||||
|
||||
err = nfsd_get_write_access(rqstp, fhp, iap);
|
||||
if (err)
|
||||
goto out;
|
||||
size_change = 1;
|
||||
host_err = locks_verify_truncate(inode, NULL, iap->ia_size);
|
||||
if (host_err) {
|
||||
put_write_access(inode);
|
||||
goto out_nfserr;
|
||||
}
|
||||
}
|
||||
|
||||
/* sanitize the mode change */
|
||||
if (iap->ia_valid & ATTR_MODE) {
|
||||
iap->ia_mode &= S_IALLUGO;
|
||||
iap->ia_mode |= (inode->i_mode & ~S_IALLUGO);
|
||||
}
|
||||
|
||||
/* Revoke setuid/setgid on chown */
|
||||
if (!S_ISDIR(inode->i_mode) &&
|
||||
(((iap->ia_valid & ATTR_UID) && !uid_eq(iap->ia_uid, inode->i_uid)) ||
|
||||
((iap->ia_valid & ATTR_GID) && !gid_eq(iap->ia_gid, inode->i_gid)))) {
|
||||
iap->ia_valid |= ATTR_KILL_PRIV;
|
||||
if (iap->ia_valid & ATTR_MODE) {
|
||||
/* we're setting mode too, just clear the s*id bits */
|
||||
iap->ia_mode &= ~S_ISUID;
|
||||
if (iap->ia_mode & S_IXGRP)
|
||||
iap->ia_mode &= ~S_ISGID;
|
||||
} else {
|
||||
/* set ATTR_KILL_* bits and let VFS handle it */
|
||||
iap->ia_valid |= (ATTR_KILL_SUID | ATTR_KILL_SGID);
|
||||
}
|
||||
}
|
||||
|
||||
/* Change the attributes. */
|
||||
|
||||
iap->ia_valid |= ATTR_CTIME;
|
||||
|
||||
err = nfserr_notsync;
|
||||
|
|
Loading…
Reference in a new issue