Merge branch 'for-3.13' of git://linux-nfs.org/~bfields/linux
Pull nfsd bugfixes from Bruce Fields: "A couple nfsd bugfixes" * 'for-3.13' of git://linux-nfs.org/~bfields/linux: nfsd4: fix xdr decoding of large non-write compounds nfsd: make sure to balance get/put_write_access nfsd: split up nfsd_setattr
This commit is contained in:
@@ -141,8 +141,8 @@ xdr_error: \
|
|||||||
|
|
||||||
static void next_decode_page(struct nfsd4_compoundargs *argp)
|
static void next_decode_page(struct nfsd4_compoundargs *argp)
|
||||||
{
|
{
|
||||||
argp->pagelist++;
|
|
||||||
argp->p = page_address(argp->pagelist[0]);
|
argp->p = page_address(argp->pagelist[0]);
|
||||||
|
argp->pagelist++;
|
||||||
if (argp->pagelen < PAGE_SIZE) {
|
if (argp->pagelen < PAGE_SIZE) {
|
||||||
argp->end = argp->p + (argp->pagelen>>2);
|
argp->end = argp->p + (argp->pagelen>>2);
|
||||||
argp->pagelen = 0;
|
argp->pagelen = 0;
|
||||||
@@ -1229,6 +1229,7 @@ nfsd4_decode_write(struct nfsd4_compoundargs *argp, struct nfsd4_write *write)
|
|||||||
len -= pages * PAGE_SIZE;
|
len -= pages * PAGE_SIZE;
|
||||||
|
|
||||||
argp->p = (__be32 *)page_address(argp->pagelist[0]);
|
argp->p = (__be32 *)page_address(argp->pagelist[0]);
|
||||||
|
argp->pagelist++;
|
||||||
argp->end = argp->p + XDR_QUADLEN(PAGE_SIZE);
|
argp->end = argp->p + XDR_QUADLEN(PAGE_SIZE);
|
||||||
}
|
}
|
||||||
argp->p += XDR_QUADLEN(len);
|
argp->p += XDR_QUADLEN(len);
|
||||||
|
215
fs/nfsd/vfs.c
215
fs/nfsd/vfs.c
@@ -298,8 +298,104 @@ commit_metadata(struct svc_fh *fhp)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set various file attributes.
|
* Go over the attributes and take care of the small differences between
|
||||||
* N.B. After this call fhp needs an fh_put
|
* 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
|
__be32
|
||||||
nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap,
|
nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap,
|
||||||
@@ -333,114 +429,43 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap,
|
|||||||
if (!iap->ia_valid)
|
if (!iap->ia_valid)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
|
nfsd_sanitize_attrs(inode, iap);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* NFSv2 does not differentiate between "set-[ac]time-to-now"
|
* The size case is special, it changes the file in addition to the
|
||||||
* which only requires access, and "set-[ac]time-to-X" which
|
* attributes.
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
if (iap->ia_valid & ATTR_SIZE) {
|
if (iap->ia_valid & ATTR_SIZE) {
|
||||||
if (iap->ia_size < inode->i_size) {
|
err = nfsd_get_write_access(rqstp, fhp, iap);
|
||||||
err = nfsd_permission(rqstp, fhp->fh_export, dentry,
|
if (err)
|
||||||
NFSD_MAY_TRUNC|NFSD_MAY_OWNER_OVERRIDE);
|
goto out;
|
||||||
if (err)
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
host_err = get_write_access(inode);
|
|
||||||
if (host_err)
|
|
||||||
goto out_nfserr;
|
|
||||||
|
|
||||||
size_change = 1;
|
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;
|
iap->ia_valid |= ATTR_CTIME;
|
||||||
|
|
||||||
err = nfserr_notsync;
|
if (check_guard && guardtime != inode->i_ctime.tv_sec) {
|
||||||
if (!check_guard || guardtime == inode->i_ctime.tv_sec) {
|
err = nfserr_notsync;
|
||||||
host_err = nfsd_break_lease(inode);
|
goto out_put_write_access;
|
||||||
if (host_err)
|
|
||||||
goto out_nfserr;
|
|
||||||
fh_lock(fhp);
|
|
||||||
|
|
||||||
host_err = notify_change(dentry, iap, NULL);
|
|
||||||
err = nfserrno(host_err);
|
|
||||||
fh_unlock(fhp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
host_err = nfsd_break_lease(inode);
|
||||||
|
if (host_err)
|
||||||
|
goto out_put_write_access_nfserror;
|
||||||
|
|
||||||
|
fh_lock(fhp);
|
||||||
|
host_err = notify_change(dentry, iap, NULL);
|
||||||
|
fh_unlock(fhp);
|
||||||
|
|
||||||
|
out_put_write_access_nfserror:
|
||||||
|
err = nfserrno(host_err);
|
||||||
|
out_put_write_access:
|
||||||
if (size_change)
|
if (size_change)
|
||||||
put_write_access(inode);
|
put_write_access(inode);
|
||||||
if (!err)
|
if (!err)
|
||||||
commit_metadata(fhp);
|
commit_metadata(fhp);
|
||||||
out:
|
out:
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
out_nfserr:
|
|
||||||
err = nfserrno(host_err);
|
|
||||||
goto out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(CONFIG_NFSD_V2_ACL) || \
|
#if defined(CONFIG_NFSD_V2_ACL) || \
|
||||||
|
Reference in New Issue
Block a user