diff options
author | Andy Adamson <andros@netapp.com> | 2011-12-07 11:55:27 -0500 |
---|---|---|
committer | Trond Myklebust <Trond.Myklebust@netapp.com> | 2012-01-05 10:42:42 -0500 |
commit | bf118a342f10dafe44b14451a1392c3254629a1f (patch) | |
tree | 50c2800c7c92f8e47e4a0a440e2a97847f678e19 /fs/nfs/nfs4proc.c | |
parent | 3476f114addb7b96912840a234702f660a1f460b (diff) |
NFSv4: include bitmap in nfsv4 get acl data
The NFSv4 bitmap size is unbounded: a server can return an arbitrary
sized bitmap in an FATTR4_WORD0_ACL request. Replace using the
nfs4_fattr_bitmap_maxsz as a guess to the maximum bitmask returned by a server
with the inclusion of the bitmap (xdr length plus bitmasks) and the acl data
xdr length to the (cached) acl page data.
This is a general solution to commit e5012d1f "NFSv4.1: update
nfs4_fattr_bitmap_maxsz" and fixes hitting a BUG_ON in xdr_shrink_bufhead
when getting ACLs.
Fix a bug in decode_getacl that returned -EINVAL on ACLs > page when getxattr
was called with a NULL buffer, preventing ACL > PAGE_SIZE from being retrieved.
Cc: stable@kernel.org
Signed-off-by: Andy Adamson <andros@netapp.com>
Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
Diffstat (limited to 'fs/nfs/nfs4proc.c')
-rw-r--r-- | fs/nfs/nfs4proc.c | 96 |
1 files changed, 57 insertions, 39 deletions
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index fcc2408d7ab0..3b1080118452 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c | |||
@@ -3426,19 +3426,6 @@ static inline int nfs4_server_supports_acls(struct nfs_server *server) | |||
3426 | */ | 3426 | */ |
3427 | #define NFS4ACL_MAXPAGES (XATTR_SIZE_MAX >> PAGE_CACHE_SHIFT) | 3427 | #define NFS4ACL_MAXPAGES (XATTR_SIZE_MAX >> PAGE_CACHE_SHIFT) |
3428 | 3428 | ||
3429 | static void buf_to_pages(const void *buf, size_t buflen, | ||
3430 | struct page **pages, unsigned int *pgbase) | ||
3431 | { | ||
3432 | const void *p = buf; | ||
3433 | |||
3434 | *pgbase = offset_in_page(buf); | ||
3435 | p -= *pgbase; | ||
3436 | while (p < buf + buflen) { | ||
3437 | *(pages++) = virt_to_page(p); | ||
3438 | p += PAGE_CACHE_SIZE; | ||
3439 | } | ||
3440 | } | ||
3441 | |||
3442 | static int buf_to_pages_noslab(const void *buf, size_t buflen, | 3429 | static int buf_to_pages_noslab(const void *buf, size_t buflen, |
3443 | struct page **pages, unsigned int *pgbase) | 3430 | struct page **pages, unsigned int *pgbase) |
3444 | { | 3431 | { |
@@ -3535,9 +3522,19 @@ out: | |||
3535 | nfs4_set_cached_acl(inode, acl); | 3522 | nfs4_set_cached_acl(inode, acl); |
3536 | } | 3523 | } |
3537 | 3524 | ||
3525 | /* | ||
3526 | * The getxattr API returns the required buffer length when called with a | ||
3527 | * NULL buf. The NFSv4 acl tool then calls getxattr again after allocating | ||
3528 | * the required buf. On a NULL buf, we send a page of data to the server | ||
3529 | * guessing that the ACL request can be serviced by a page. If so, we cache | ||
3530 | * up to the page of ACL data, and the 2nd call to getxattr is serviced by | ||
3531 | * the cache. If not so, we throw away the page, and cache the required | ||
3532 | * length. The next getxattr call will then produce another round trip to | ||
3533 | * the server, this time with the input buf of the required size. | ||
3534 | */ | ||
3538 | static ssize_t __nfs4_get_acl_uncached(struct inode *inode, void *buf, size_t buflen) | 3535 | static ssize_t __nfs4_get_acl_uncached(struct inode *inode, void *buf, size_t buflen) |
3539 | { | 3536 | { |
3540 | struct page *pages[NFS4ACL_MAXPAGES]; | 3537 | struct page *pages[NFS4ACL_MAXPAGES] = {NULL, }; |
3541 | struct nfs_getaclargs args = { | 3538 | struct nfs_getaclargs args = { |
3542 | .fh = NFS_FH(inode), | 3539 | .fh = NFS_FH(inode), |
3543 | .acl_pages = pages, | 3540 | .acl_pages = pages, |
@@ -3552,41 +3549,60 @@ static ssize_t __nfs4_get_acl_uncached(struct inode *inode, void *buf, size_t bu | |||
3552 | .rpc_argp = &args, | 3549 | .rpc_argp = &args, |
3553 | .rpc_resp = &res, | 3550 | .rpc_resp = &res, |
3554 | }; | 3551 | }; |
3555 | struct page *localpage = NULL; | 3552 | int ret = -ENOMEM, npages, i, acl_len = 0; |
3556 | int ret; | ||
3557 | 3553 | ||
3558 | if (buflen < PAGE_SIZE) { | 3554 | npages = (buflen + PAGE_SIZE - 1) >> PAGE_SHIFT; |
3559 | /* As long as we're doing a round trip to the server anyway, | 3555 | /* As long as we're doing a round trip to the server anyway, |
3560 | * let's be prepared for a page of acl data. */ | 3556 | * let's be prepared for a page of acl data. */ |
3561 | localpage = alloc_page(GFP_KERNEL); | 3557 | if (npages == 0) |
3562 | resp_buf = page_address(localpage); | 3558 | npages = 1; |
3563 | if (localpage == NULL) | 3559 | |
3564 | return -ENOMEM; | 3560 | for (i = 0; i < npages; i++) { |
3565 | args.acl_pages[0] = localpage; | 3561 | pages[i] = alloc_page(GFP_KERNEL); |
3566 | args.acl_pgbase = 0; | 3562 | if (!pages[i]) |
3567 | args.acl_len = PAGE_SIZE; | 3563 | goto out_free; |
3568 | } else { | 3564 | } |
3569 | resp_buf = buf; | 3565 | if (npages > 1) { |
3570 | buf_to_pages(buf, buflen, args.acl_pages, &args.acl_pgbase); | 3566 | /* for decoding across pages */ |
3567 | args.acl_scratch = alloc_page(GFP_KERNEL); | ||
3568 | if (!args.acl_scratch) | ||
3569 | goto out_free; | ||
3571 | } | 3570 | } |
3572 | ret = nfs4_call_sync(NFS_SERVER(inode)->client, NFS_SERVER(inode), &msg, &args.seq_args, &res.seq_res, 0); | 3571 | args.acl_len = npages * PAGE_SIZE; |
3572 | args.acl_pgbase = 0; | ||
3573 | /* Let decode_getfacl know not to fail if the ACL data is larger than | ||
3574 | * the page we send as a guess */ | ||
3575 | if (buf == NULL) | ||
3576 | res.acl_flags |= NFS4_ACL_LEN_REQUEST; | ||
3577 | resp_buf = page_address(pages[0]); | ||
3578 | |||
3579 | dprintk("%s buf %p buflen %ld npages %d args.acl_len %ld\n", | ||
3580 | __func__, buf, buflen, npages, args.acl_len); | ||
3581 | ret = nfs4_call_sync(NFS_SERVER(inode)->client, NFS_SERVER(inode), | ||
3582 | &msg, &args.seq_args, &res.seq_res, 0); | ||
3573 | if (ret) | 3583 | if (ret) |
3574 | goto out_free; | 3584 | goto out_free; |
3575 | if (res.acl_len > args.acl_len) | 3585 | |
3576 | nfs4_write_cached_acl(inode, NULL, res.acl_len); | 3586 | acl_len = res.acl_len - res.acl_data_offset; |
3587 | if (acl_len > args.acl_len) | ||
3588 | nfs4_write_cached_acl(inode, NULL, acl_len); | ||
3577 | else | 3589 | else |
3578 | nfs4_write_cached_acl(inode, resp_buf, res.acl_len); | 3590 | nfs4_write_cached_acl(inode, resp_buf + res.acl_data_offset, |
3591 | acl_len); | ||
3579 | if (buf) { | 3592 | if (buf) { |
3580 | ret = -ERANGE; | 3593 | ret = -ERANGE; |
3581 | if (res.acl_len > buflen) | 3594 | if (acl_len > buflen) |
3582 | goto out_free; | 3595 | goto out_free; |
3583 | if (localpage) | 3596 | _copy_from_pages(buf, pages, res.acl_data_offset, |
3584 | memcpy(buf, resp_buf, res.acl_len); | 3597 | res.acl_len); |
3585 | } | 3598 | } |
3586 | ret = res.acl_len; | 3599 | ret = acl_len; |
3587 | out_free: | 3600 | out_free: |
3588 | if (localpage) | 3601 | for (i = 0; i < npages; i++) |
3589 | __free_page(localpage); | 3602 | if (pages[i]) |
3603 | __free_page(pages[i]); | ||
3604 | if (args.acl_scratch) | ||
3605 | __free_page(args.acl_scratch); | ||
3590 | return ret; | 3606 | return ret; |
3591 | } | 3607 | } |
3592 | 3608 | ||
@@ -3617,6 +3633,8 @@ static ssize_t nfs4_proc_get_acl(struct inode *inode, void *buf, size_t buflen) | |||
3617 | nfs_zap_acl_cache(inode); | 3633 | nfs_zap_acl_cache(inode); |
3618 | ret = nfs4_read_cached_acl(inode, buf, buflen); | 3634 | ret = nfs4_read_cached_acl(inode, buf, buflen); |
3619 | if (ret != -ENOENT) | 3635 | if (ret != -ENOENT) |
3636 | /* -ENOENT is returned if there is no ACL or if there is an ACL | ||
3637 | * but no cached acl data, just the acl length */ | ||
3620 | return ret; | 3638 | return ret; |
3621 | return nfs4_get_acl_uncached(inode, buf, buflen); | 3639 | return nfs4_get_acl_uncached(inode, buf, buflen); |
3622 | } | 3640 | } |