aboutsummaryrefslogtreecommitdiffstats
path: root/fs/nfsd/vfs.c
diff options
context:
space:
mode:
authorChristoph Hellwig <hch@lst.de>2017-01-24 03:22:41 -0500
committerJ. Bruce Fields <bfields@redhat.com>2017-01-31 12:29:24 -0500
commit41f53350a0f36a7b8e31bec0d0ca907e028ab4cd (patch)
tree086931a2815c160d45576b05254ded0aa8c2cbf4 /fs/nfsd/vfs.c
parentd19fb70dd68c4e960e2ac09b0b9c79dfdeefa726 (diff)
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>
Diffstat (limited to 'fs/nfsd/vfs.c')
-rw-r--r--fs/nfsd/vfs.c97
1 files changed, 37 insertions, 60 deletions
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index 26c6fdb4bf67..ca13236dbb1f 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -332,37 +332,6 @@ nfsd_sanitize_attrs(struct inode *inode, struct iattr *iap)
332 } 332 }
333} 333}
334 334
335static __be32
336nfsd_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
360out_put_write_access:
361 put_write_access(inode);
362out_nfserrno:
363 return nfserrno(host_err);
364}
365
366/* 335/*
367 * Set various file attributes. After this call fhp needs an fh_put. 336 * Set various file attributes. After this call fhp needs an fh_put.
368 */ 337 */
@@ -377,7 +346,6 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap,
377 __be32 err; 346 __be32 err;
378 int host_err; 347 int host_err;
379 bool get_write_count; 348 bool get_write_count;
380 int size_change = 0;
381 349
382 if (iap->ia_valid & (ATTR_ATIME | ATTR_MTIME | ATTR_SIZE)) 350 if (iap->ia_valid & (ATTR_ATIME | ATTR_MTIME | ATTR_SIZE))
383 accmode |= NFSD_MAY_WRITE|NFSD_MAY_OWNER_OVERRIDE; 351 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,
390 /* Get inode */ 358 /* Get inode */
391 err = fh_verify(rqstp, fhp, ftype, accmode); 359 err = fh_verify(rqstp, fhp, ftype, accmode);
392 if (err) 360 if (err)
393 goto out; 361 return err;
394 if (get_write_count) { 362 if (get_write_count) {
395 host_err = fh_want_write(fhp); 363 host_err = fh_want_write(fhp);
396 if (host_err) 364 if (host_err)
397 return nfserrno(host_err); 365 goto out_host_err;
398 } 366 }
399 367
400 dentry = fhp->fh_dentry; 368 dentry = fhp->fh_dentry;
@@ -405,50 +373,59 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap,
405 iap->ia_valid &= ~ATTR_MODE; 373 iap->ia_valid &= ~ATTR_MODE;
406 374
407 if (!iap->ia_valid) 375 if (!iap->ia_valid)
408 goto out; 376 return 0;
409 377
410 nfsd_sanitize_attrs(inode, iap); 378 nfsd_sanitize_attrs(inode, iap);
411 379
380 if (check_guard && guardtime != inode->i_ctime.tv_sec)
381 return nfserr_notsync;
382
412 /* 383 /*
413 * The size case is special, it changes the file in addition to the 384 * 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.
415 */ 389 */
416 if (iap->ia_valid & ATTR_SIZE) { 390 if (iap->ia_valid & ATTR_SIZE) {
417 err = nfsd_get_write_access(rqstp, fhp, iap); 391 struct path path = {
418 if (err) 392 .mnt = fhp->fh_export->ex_path.mnt,
419 goto out; 393 .dentry = dentry,
420 size_change = 1; 394 };
395 bool implicit_mtime = false;
421 396
422 /* 397 /*
423 * RFC5661, Section 18.30.4: 398 * vfs_truncate implicity updates the mtime IFF the file size
424 * Changing the size of a file with SETATTR indirectly 399 * actually changes. Avoid the additional seattr call below if
425 * changes the time_modify and change attributes. 400 * the only other attribute that the client sends is the mtime.
426 *
427 * (and similar for the older RFCs)
428 */ 401 */
429 if (iap->ia_size != i_size_read(inode)) 402 if (iap->ia_size != i_size_read(inode) &&
430 iap->ia_valid |= ATTR_MTIME; 403 ((iap->ia_valid & ~(ATTR_SIZE | ATTR_MTIME)) == 0))
431 } 404 implicit_mtime = true;
432 405
433 iap->ia_valid |= ATTR_CTIME; 406 host_err = vfs_truncate(&path, iap->ia_size);
407 if (host_err)
408 goto out_host_err;
434 409
435 if (check_guard && guardtime != inode->i_ctime.tv_sec) { 410 iap->ia_valid &= ~ATTR_SIZE;
436 err = nfserr_notsync; 411 if (implicit_mtime)
437 goto out_put_write_access; 412 iap->ia_valid &= ~ATTR_MTIME;
413 if (!iap->ia_valid)
414 goto done;
438 } 415 }
439 416
417 iap->ia_valid |= ATTR_CTIME;
418
440 fh_lock(fhp); 419 fh_lock(fhp);
441 host_err = notify_change(dentry, iap, NULL); 420 host_err = notify_change(dentry, iap, NULL);
442 fh_unlock(fhp); 421 fh_unlock(fhp);
443 err = nfserrno(host_err); 422 if (host_err)
423 goto out_host_err;
444 424
445out_put_write_access: 425done:
446 if (size_change) 426 host_err = commit_metadata(fhp);
447 put_write_access(inode); 427out_host_err:
448 if (!err) 428 return nfserrno(host_err);
449 err = nfserrno(commit_metadata(fhp));
450out:
451 return err;
452} 429}
453 430
454#if defined(CONFIG_NFSD_V4) 431#if defined(CONFIG_NFSD_V4)