OSDN Git Service

nfsd4: fix encoding of out-of-space replies
authorJ. Bruce Fields <bfields@redhat.com>
Fri, 7 Mar 2014 01:39:29 +0000 (20:39 -0500)
committerJ. Bruce Fields <bfields@redhat.com>
Tue, 27 May 2014 15:09:08 +0000 (11:09 -0400)
If nfsd4_check_resp_size() returns an error then we should really be
truncating the reply here, otherwise we may leave extra garbage at the
end of the rpc reply.

Also add a warning to catch any cases where our reply-size estimates may
be wrong in the case of a non-idempotent operation.

Signed-off-by: J. Bruce Fields <bfields@redhat.com>
fs/nfsd/nfs4proc.c
fs/nfsd/nfs4xdr.c
fs/nfsd/xdr4.h

index 747d6a8..bf8cddf 100644 (file)
@@ -1171,9 +1171,7 @@ struct nfsd4_operation {
 
 static struct nfsd4_operation nfsd4_ops[];
 
-#ifdef NFSD_DEBUG
 static const char *nfsd4_op_name(unsigned opnum);
-#endif
 
 /*
  * Enforce NFSv4.1 COMPOUND ordering rules:
@@ -1859,14 +1857,21 @@ static struct nfsd4_operation nfsd4_ops[] = {
        },
 };
 
-#ifdef NFSD_DEBUG
+void warn_on_nonidempotent_op(struct nfsd4_op *op)
+{
+       if (OPDESC(op)->op_flags & OP_MODIFIES_SOMETHING) {
+               pr_err("unable to encode reply to nonidempotent op %d (%s)\n",
+                       op->opnum, nfsd4_op_name(op->opnum));
+               WARN_ON_ONCE(1);
+       }
+}
+
 static const char *nfsd4_op_name(unsigned opnum)
 {
        if (opnum < ARRAY_SIZE(nfsd4_ops))
                return nfsd4_ops[opnum].op_name;
        return "unknown_operation";
 }
-#endif
 
 #define nfsd4_voidres                  nfsd4_voidargs
 struct nfsd4_voidargs { int dummy; };
index 1879250..24ba652 100644 (file)
@@ -3637,6 +3637,7 @@ nfsd4_encode_operation(struct nfsd4_compoundres *resp, struct nfsd4_op *op)
 {
        struct nfs4_stateowner *so = resp->cstate.replay_owner;
        __be32 *statp;
+       nfsd4_enc encoder;
        __be32 *p;
 
        RESERVE_SPACE(8);
@@ -3648,10 +3649,24 @@ nfsd4_encode_operation(struct nfsd4_compoundres *resp, struct nfsd4_op *op)
                goto status;
        BUG_ON(op->opnum < 0 || op->opnum >= ARRAY_SIZE(nfsd4_enc_ops) ||
               !nfsd4_enc_ops[op->opnum]);
-       op->status = nfsd4_enc_ops[op->opnum](resp, op->status, &op->u);
+       encoder = nfsd4_enc_ops[op->opnum];
+       op->status = encoder(resp, op->status, &op->u);
        /* nfsd4_check_resp_size guarantees enough room for error status */
        if (!op->status)
                op->status = nfsd4_check_resp_size(resp, 0);
+       if (op->status == nfserr_resource ||
+           op->status == nfserr_rep_too_big ||
+           op->status == nfserr_rep_too_big_to_cache) {
+               /*
+                * The operation may have already been encoded or
+                * partially encoded.  No op returns anything additional
+                * in the case of one of these three errors, so we can
+                * just truncate back to after the status.  But it's a
+                * bug if we had to do this on a non-idempotent op:
+                */
+               warn_on_nonidempotent_op(op);
+               resp->xdr.p = statp + 1;
+       }
        if (so) {
                so->so_replay.rp_status = op->status;
                so->so_replay.rp_buflen = (char *)resp->xdr.p
index f62a055..15ca477 100644 (file)
@@ -536,6 +536,8 @@ static inline bool nfsd4_last_compound_op(struct svc_rqst *rqstp)
        return argp->opcnt == resp->opcnt;
 }
 
+void warn_on_nonidempotent_op(struct nfsd4_op *op);
+
 #define NFS4_SVC_XDRSIZE               sizeof(struct nfsd4_compoundargs)
 
 static inline void