Skip to content

Commit 41f5335

Browse files
Christoph HellwigJ. Bruce Fields
authored andcommitted
nfsd: special case truncates some more
Both the NFS protocols and the Linux VFS use a setattr operation with a bitmap of attributs to set to set various file attributes including the file size and the uid/gid. The Linux syscalls never mixes size updates with unrelated updates like the uid/gid, and some file systems like XFS and GFS2 rely on the fact that truncates might not update random other attributes, and many other file systems handle the case but do not update the different attributes in the same transaction. NFSD on the other hand passes the attributes it gets on the wire more or less directly through to the VFS, leading to updates the file systems don't expect. XFS at least has an assert on the allowed attributes, which caught an unusual NFS client setting the size and group at the same time. To handle this issue properly this switches nfsd to call vfs_truncate for size changes, and then handle all other attributes through notify_change. As a side effect this also means less boilerplace code around the size change as we can now reuse the VFS code. Signed-off-by: Christoph Hellwig <hch@lst.de> Signed-off-by: J. Bruce Fields <bfields@redhat.com>
1 parent d19fb70 commit 41f5335

File tree

1 file changed

+37
-60
lines changed

1 file changed

+37
-60
lines changed

fs/nfsd/vfs.c

Lines changed: 37 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -332,37 +332,6 @@ nfsd_sanitize_attrs(struct inode *inode, struct iattr *iap)
332332
}
333333
}
334334

335-
static __be32
336-
nfsd_get_write_access(struct svc_rqst *rqstp, struct svc_fh *fhp,
337-
struct iattr *iap)
338-
{
339-
struct inode *inode = d_inode(fhp->fh_dentry);
340-
int host_err;
341-
342-
if (iap->ia_size < inode->i_size) {
343-
__be32 err;
344-
345-
err = nfsd_permission(rqstp, fhp->fh_export, fhp->fh_dentry,
346-
NFSD_MAY_TRUNC | NFSD_MAY_OWNER_OVERRIDE);
347-
if (err)
348-
return err;
349-
}
350-
351-
host_err = get_write_access(inode);
352-
if (host_err)
353-
goto out_nfserrno;
354-
355-
host_err = locks_verify_truncate(inode, NULL, iap->ia_size);
356-
if (host_err)
357-
goto out_put_write_access;
358-
return 0;
359-
360-
out_put_write_access:
361-
put_write_access(inode);
362-
out_nfserrno:
363-
return nfserrno(host_err);
364-
}
365-
366335
/*
367336
* Set various file attributes. After this call fhp needs an fh_put.
368337
*/
@@ -377,7 +346,6 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap,
377346
__be32 err;
378347
int host_err;
379348
bool get_write_count;
380-
int size_change = 0;
381349

382350
if (iap->ia_valid & (ATTR_ATIME | ATTR_MTIME | ATTR_SIZE))
383351
accmode |= NFSD_MAY_WRITE|NFSD_MAY_OWNER_OVERRIDE;
@@ -390,11 +358,11 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap,
390358
/* Get inode */
391359
err = fh_verify(rqstp, fhp, ftype, accmode);
392360
if (err)
393-
goto out;
361+
return err;
394362
if (get_write_count) {
395363
host_err = fh_want_write(fhp);
396364
if (host_err)
397-
return nfserrno(host_err);
365+
goto out_host_err;
398366
}
399367

400368
dentry = fhp->fh_dentry;
@@ -405,50 +373,59 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap,
405373
iap->ia_valid &= ~ATTR_MODE;
406374

407375
if (!iap->ia_valid)
408-
goto out;
376+
return 0;
409377

410378
nfsd_sanitize_attrs(inode, iap);
411379

380+
if (check_guard && guardtime != inode->i_ctime.tv_sec)
381+
return nfserr_notsync;
382+
412383
/*
413384
* The size case is special, it changes the file in addition to the
414-
* attributes.
385+
* attributes, and file systems don't expect it to be mixed with
386+
* "random" attribute changes. We thus split out the size change
387+
* into a separate call for vfs_truncate, and do the rest as a
388+
* a separate setattr call.
415389
*/
416390
if (iap->ia_valid & ATTR_SIZE) {
417-
err = nfsd_get_write_access(rqstp, fhp, iap);
418-
if (err)
419-
goto out;
420-
size_change = 1;
391+
struct path path = {
392+
.mnt = fhp->fh_export->ex_path.mnt,
393+
.dentry = dentry,
394+
};
395+
bool implicit_mtime = false;
421396

422397
/*
423-
* RFC5661, Section 18.30.4:
424-
* Changing the size of a file with SETATTR indirectly
425-
* changes the time_modify and change attributes.
426-
*
427-
* (and similar for the older RFCs)
398+
* vfs_truncate implicity updates the mtime IFF the file size
399+
* actually changes. Avoid the additional seattr call below if
400+
* the only other attribute that the client sends is the mtime.
428401
*/
429-
if (iap->ia_size != i_size_read(inode))
430-
iap->ia_valid |= ATTR_MTIME;
431-
}
402+
if (iap->ia_size != i_size_read(inode) &&
403+
((iap->ia_valid & ~(ATTR_SIZE | ATTR_MTIME)) == 0))
404+
implicit_mtime = true;
432405

433-
iap->ia_valid |= ATTR_CTIME;
406+
host_err = vfs_truncate(&path, iap->ia_size);
407+
if (host_err)
408+
goto out_host_err;
434409

435-
if (check_guard && guardtime != inode->i_ctime.tv_sec) {
436-
err = nfserr_notsync;
437-
goto out_put_write_access;
410+
iap->ia_valid &= ~ATTR_SIZE;
411+
if (implicit_mtime)
412+
iap->ia_valid &= ~ATTR_MTIME;
413+
if (!iap->ia_valid)
414+
goto done;
438415
}
439416

417+
iap->ia_valid |= ATTR_CTIME;
418+
440419
fh_lock(fhp);
441420
host_err = notify_change(dentry, iap, NULL);
442421
fh_unlock(fhp);
443-
err = nfserrno(host_err);
422+
if (host_err)
423+
goto out_host_err;
444424

445-
out_put_write_access:
446-
if (size_change)
447-
put_write_access(inode);
448-
if (!err)
449-
err = nfserrno(commit_metadata(fhp));
450-
out:
451-
return err;
425+
done:
426+
host_err = commit_metadata(fhp);
427+
out_host_err:
428+
return nfserrno(host_err);
452429
}
453430

454431
#if defined(CONFIG_NFSD_V4)

0 commit comments

Comments
 (0)