lkml.org 
[lkml]   [2012]   [Jan]   [16]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    Date
    From
    Subject[26/48] NFSv4: include bitmap in nfsv4 get acl data
    3.1-stable review patch.  If anyone has any objections, please let me know.

    ------------------

    From: Andy Adamson <andros@netapp.com>

    commit bf118a342f10dafe44b14451a1392c3254629a1f upstream.

    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.

    Signed-off-by: Andy Adamson <andros@netapp.com>
    Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
    Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

    ---
    fs/nfs/nfs4proc.c | 96 ++++++++++++++++++++++++++-------------------
    fs/nfs/nfs4xdr.c | 31 ++++++++++----
    include/linux/nfs_xdr.h | 5 ++
    include/linux/sunrpc/xdr.h | 2
    net/sunrpc/xdr.c | 3 -
    5 files changed, 89 insertions(+), 48 deletions(-)

    --- a/fs/nfs/nfs4proc.c
    +++ b/fs/nfs/nfs4proc.c
    @@ -3442,19 +3442,6 @@ static inline int nfs4_server_supports_a
    */
    #define NFS4ACL_MAXPAGES (XATTR_SIZE_MAX >> PAGE_CACHE_SHIFT)

    -static void buf_to_pages(const void *buf, size_t buflen,
    - struct page **pages, unsigned int *pgbase)
    -{
    - const void *p = buf;
    -
    - *pgbase = offset_in_page(buf);
    - p -= *pgbase;
    - while (p < buf + buflen) {
    - *(pages++) = virt_to_page(p);
    - p += PAGE_CACHE_SIZE;
    - }
    -}
    -
    static int buf_to_pages_noslab(const void *buf, size_t buflen,
    struct page **pages, unsigned int *pgbase)
    {
    @@ -3551,9 +3538,19 @@ out:
    nfs4_set_cached_acl(inode, acl);
    }

    +/*
    + * The getxattr API returns the required buffer length when called with a
    + * NULL buf. The NFSv4 acl tool then calls getxattr again after allocating
    + * the required buf. On a NULL buf, we send a page of data to the server
    + * guessing that the ACL request can be serviced by a page. If so, we cache
    + * up to the page of ACL data, and the 2nd call to getxattr is serviced by
    + * the cache. If not so, we throw away the page, and cache the required
    + * length. The next getxattr call will then produce another round trip to
    + * the server, this time with the input buf of the required size.
    + */
    static ssize_t __nfs4_get_acl_uncached(struct inode *inode, void *buf, size_t buflen)
    {
    - struct page *pages[NFS4ACL_MAXPAGES];
    + struct page *pages[NFS4ACL_MAXPAGES] = {NULL, };
    struct nfs_getaclargs args = {
    .fh = NFS_FH(inode),
    .acl_pages = pages,
    @@ -3568,41 +3565,60 @@ static ssize_t __nfs4_get_acl_uncached(s
    .rpc_argp = &args,
    .rpc_resp = &res,
    };
    - struct page *localpage = NULL;
    - int ret;
    + int ret = -ENOMEM, npages, i, acl_len = 0;

    - if (buflen < PAGE_SIZE) {
    - /* As long as we're doing a round trip to the server anyway,
    - * let's be prepared for a page of acl data. */
    - localpage = alloc_page(GFP_KERNEL);
    - resp_buf = page_address(localpage);
    - if (localpage == NULL)
    - return -ENOMEM;
    - args.acl_pages[0] = localpage;
    - args.acl_pgbase = 0;
    - args.acl_len = PAGE_SIZE;
    - } else {
    - resp_buf = buf;
    - buf_to_pages(buf, buflen, args.acl_pages, &args.acl_pgbase);
    + npages = (buflen + PAGE_SIZE - 1) >> PAGE_SHIFT;
    + /* As long as we're doing a round trip to the server anyway,
    + * let's be prepared for a page of acl data. */
    + if (npages == 0)
    + npages = 1;
    +
    + for (i = 0; i < npages; i++) {
    + pages[i] = alloc_page(GFP_KERNEL);
    + if (!pages[i])
    + goto out_free;
    }
    - ret = nfs4_call_sync(NFS_SERVER(inode)->client, NFS_SERVER(inode), &msg, &args.seq_args, &res.seq_res, 0);
    + if (npages > 1) {
    + /* for decoding across pages */
    + args.acl_scratch = alloc_page(GFP_KERNEL);
    + if (!args.acl_scratch)
    + goto out_free;
    + }
    + args.acl_len = npages * PAGE_SIZE;
    + args.acl_pgbase = 0;
    + /* Let decode_getfacl know not to fail if the ACL data is larger than
    + * the page we send as a guess */
    + if (buf == NULL)
    + res.acl_flags |= NFS4_ACL_LEN_REQUEST;
    + resp_buf = page_address(pages[0]);
    +
    + dprintk("%s buf %p buflen %ld npages %d args.acl_len %ld\n",
    + __func__, buf, buflen, npages, args.acl_len);
    + ret = nfs4_call_sync(NFS_SERVER(inode)->client, NFS_SERVER(inode),
    + &msg, &args.seq_args, &res.seq_res, 0);
    if (ret)
    goto out_free;
    - if (res.acl_len > args.acl_len)
    - nfs4_write_cached_acl(inode, NULL, res.acl_len);
    +
    + acl_len = res.acl_len - res.acl_data_offset;
    + if (acl_len > args.acl_len)
    + nfs4_write_cached_acl(inode, NULL, acl_len);
    else
    - nfs4_write_cached_acl(inode, resp_buf, res.acl_len);
    + nfs4_write_cached_acl(inode, resp_buf + res.acl_data_offset,
    + acl_len);
    if (buf) {
    ret = -ERANGE;
    - if (res.acl_len > buflen)
    + if (acl_len > buflen)
    goto out_free;
    - if (localpage)
    - memcpy(buf, resp_buf, res.acl_len);
    + _copy_from_pages(buf, pages, res.acl_data_offset,
    + res.acl_len);
    }
    - ret = res.acl_len;
    + ret = acl_len;
    out_free:
    - if (localpage)
    - __free_page(localpage);
    + for (i = 0; i < npages; i++)
    + if (pages[i])
    + __free_page(pages[i]);
    + if (args.acl_scratch)
    + __free_page(args.acl_scratch);
    return ret;
    }

    @@ -3633,6 +3649,8 @@ static ssize_t nfs4_proc_get_acl(struct
    nfs_zap_acl_cache(inode);
    ret = nfs4_read_cached_acl(inode, buf, buflen);
    if (ret != -ENOENT)
    + /* -ENOENT is returned if there is no ACL or if there is an ACL
    + * but no cached acl data, just the acl length */
    return ret;
    return nfs4_get_acl_uncached(inode, buf, buflen);
    }
    --- a/fs/nfs/nfs4xdr.c
    +++ b/fs/nfs/nfs4xdr.c
    @@ -2517,11 +2517,13 @@ static void nfs4_xdr_enc_getacl(struct r
    encode_compound_hdr(xdr, req, &hdr);
    encode_sequence(xdr, &args->seq_args, &hdr);
    encode_putfh(xdr, args->fh, &hdr);
    - replen = hdr.replen + op_decode_hdr_maxsz + nfs4_fattr_bitmap_maxsz + 1;
    + replen = hdr.replen + op_decode_hdr_maxsz + 1;
    encode_getattr_two(xdr, FATTR4_WORD0_ACL, 0, &hdr);

    xdr_inline_pages(&req->rq_rcv_buf, replen << 2,
    args->acl_pages, args->acl_pgbase, args->acl_len);
    + xdr_set_scratch_buffer(xdr, page_address(args->acl_scratch), PAGE_SIZE);
    +
    encode_nops(&hdr);
    }

    @@ -4957,17 +4959,18 @@ decode_restorefh(struct xdr_stream *xdr)
    }

    static int decode_getacl(struct xdr_stream *xdr, struct rpc_rqst *req,
    - size_t *acl_len)
    + struct nfs_getaclres *res)
    {
    - __be32 *savep;
    + __be32 *savep, *bm_p;
    uint32_t attrlen,
    bitmap[3] = {0};
    struct kvec *iov = req->rq_rcv_buf.head;
    int status;

    - *acl_len = 0;
    + res->acl_len = 0;
    if ((status = decode_op_hdr(xdr, OP_GETATTR)) != 0)
    goto out;
    + bm_p = xdr->p;
    if ((status = decode_attr_bitmap(xdr, bitmap)) != 0)
    goto out;
    if ((status = decode_attr_length(xdr, &attrlen, &savep)) != 0)
    @@ -4979,18 +4982,30 @@ static int decode_getacl(struct xdr_stre
    size_t hdrlen;
    u32 recvd;

    + /* The bitmap (xdr len + bitmaps) and the attr xdr len words
    + * are stored with the acl data to handle the problem of
    + * variable length bitmaps.*/
    + xdr->p = bm_p;
    + res->acl_data_offset = be32_to_cpup(bm_p) + 2;
    + res->acl_data_offset <<= 2;
    +
    /* We ignore &savep and don't do consistency checks on
    * the attr length. Let userspace figure it out.... */
    hdrlen = (u8 *)xdr->p - (u8 *)iov->iov_base;
    + attrlen += res->acl_data_offset;
    recvd = req->rq_rcv_buf.len - hdrlen;
    if (attrlen > recvd) {
    - dprintk("NFS: server cheating in getattr"
    - " acl reply: attrlen %u > recvd %u\n",
    + if (res->acl_flags & NFS4_ACL_LEN_REQUEST) {
    + /* getxattr interface called with a NULL buf */
    + res->acl_len = attrlen;
    + goto out;
    + }
    + dprintk("NFS: acl reply: attrlen %u > recvd %u\n",
    attrlen, recvd);
    return -EINVAL;
    }
    xdr_read_pages(xdr, attrlen);
    - *acl_len = attrlen;
    + res->acl_len = attrlen;
    } else
    status = -EOPNOTSUPP;

    @@ -6028,7 +6043,7 @@ nfs4_xdr_dec_getacl(struct rpc_rqst *rqs
    status = decode_putfh(xdr);
    if (status)
    goto out;
    - status = decode_getacl(xdr, rqstp, &res->acl_len);
    + status = decode_getacl(xdr, rqstp, res);

    out:
    return status;
    --- a/include/linux/nfs_xdr.h
    +++ b/include/linux/nfs_xdr.h
    @@ -602,11 +602,16 @@ struct nfs_getaclargs {
    size_t acl_len;
    unsigned int acl_pgbase;
    struct page ** acl_pages;
    + struct page * acl_scratch;
    struct nfs4_sequence_args seq_args;
    };

    +/* getxattr ACL interface flags */
    +#define NFS4_ACL_LEN_REQUEST 0x0001 /* zero length getxattr buffer */
    struct nfs_getaclres {
    size_t acl_len;
    + size_t acl_data_offset;
    + int acl_flags;
    struct nfs4_sequence_res seq_res;
    };

    --- a/include/linux/sunrpc/xdr.h
    +++ b/include/linux/sunrpc/xdr.h
    @@ -191,6 +191,8 @@ extern int xdr_decode_array2(struct xdr_
    struct xdr_array2_desc *desc);
    extern int xdr_encode_array2(struct xdr_buf *buf, unsigned int base,
    struct xdr_array2_desc *desc);
    +extern void _copy_from_pages(char *p, struct page **pages, size_t pgbase,
    + size_t len);

    /*
    * Provide some simple tools for XDR buffer overflow-checking etc.
    --- a/net/sunrpc/xdr.c
    +++ b/net/sunrpc/xdr.c
    @@ -296,7 +296,7 @@ _copy_to_pages(struct page **pages, size
    * Copies data into an arbitrary memory location from an array of pages
    * The copy is assumed to be non-overlapping.
    */
    -static void
    +void
    _copy_from_pages(char *p, struct page **pages, size_t pgbase, size_t len)
    {
    struct page **pgfrom;
    @@ -324,6 +324,7 @@ _copy_from_pages(char *p, struct page **

    } while ((len -= copy) != 0);
    }
    +EXPORT_SYMBOL_GPL(_copy_from_pages);

    /*
    * xdr_shrink_bufhead



    \
     
     \ /
      Last update: 2012-01-16 20:07    [W:0.037 / U:96.348 seconds]
    ©2003-2016 Jasper Spaans. hosted at Digital OceanAdvertise on this site