diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index 7e00116..a856f30 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -1412,6 +1412,11 @@ static struct nfsd4_operation nfsd4_ops[] = {
 		.op_flags = OP_HANDLES_WRONGSEC,
 		.op_name = "OP_SECINFO_NO_NAME",
 	},
+	[OP_TEST_STATEID] = {
+		.op_func = (nfsd4op_func)nfsd4_test_stateid,
+		.op_flags = ALLOWED_WITHOUT_FH,
+		.op_name = "OP_TEST_STATEID",
+	},
 	[OP_FREE_STATEID] = {
 		.op_func = (nfsd4op_func)nfsd4_free_stateid,
 		.op_flags = ALLOWED_WITHOUT_FH,
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index 6beec13..c7bb3ce 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -37,6 +37,7 @@
 #include <linux/slab.h>
 #include <linux/namei.h>
 #include <linux/swap.h>
+#include <linux/pagemap.h>
 #include <linux/sunrpc/svcauth_gss.h>
 #include <linux/sunrpc/clnt.h>
 #include "xdr4.h"
@@ -3140,6 +3141,35 @@ static int is_delegation_stateid(stateid_t *stateid)
 	return stateid->si_fileid == 0;
 }
 
+static __be32 nfs4_validate_stateid(stateid_t *stateid, int flags, struct file **filpp, struct svc_fh *current_fh)
+{
+	struct nfs4_stateid *stp = NULL;
+	__be32 status = nfserr_stale_stateid;
+
+	if (STALE_STATEID(stateid))
+		goto out;
+
+	status = nfserr_expired;
+	stp = search_for_stateid(stateid);
+	if (!stp)
+		goto out;
+	status = nfserr_bad_stateid;
+
+	if (!stp->st_stateowner->so_confirmed)
+		goto out;
+
+	status = check_stateid_generation(stateid, &stp->st_stateid, flags);
+	if (status)
+		goto out;
+
+	status = nfs4_check_openmode(stp, flags);
+	if (status)
+		goto out;
+	status = nfs_ok;
+out:
+	return status;
+}
+
 /*
 * Checks for stateid operations
 */
@@ -3220,6 +3250,42 @@ out:
 }
 
 /*
+ * Test if the stateid is valid
+ */
+__be32
+nfsd4_test_stateid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
+		   struct nfsd4_test_stateid *test_stateid)
+{
+	stateid_t *stateid = NULL;
+	int *resp = NULL;
+	struct svc_fh *current_fh;
+	unsigned int cur_id_page = 0;
+	unsigned int cur_resp_page = 0;
+	int i;
+	unsigned int lock_flags = LOCK_STATE | RD_STATE | WR_STATE;
+	unsigned int open_flags = OPEN_STATE | RD_STATE | WR_STATE;
+
+	current_fh = &cstate->current_fh;
+
+	for (i = 0; i < test_stateid->ts_num_ids; i++) {
+		if ((i % test_stateid->ts_stateids->items_per_page) == 0) {
+			stateid = (stateid_t *)test_stateid->ts_stateids->items[cur_id_page].first_item;
+			cur_id_page++;
+		}
+		if ((i % test_stateid->ts_valid->items_per_page) == 0) {
+			resp = (int *)test_stateid->ts_valid->items[cur_resp_page].first_item;
+			cur_resp_page++;
+		}
+		*resp = nfs4_validate_stateid(stateid, lock_flags, NULL, current_fh);
+		if (!*resp)
+			*resp = nfs4_validate_stateid(stateid, open_flags, NULL, current_fh);
+		stateid++;
+		resp++;
+	}
+	return nfs_ok;
+}
+
+/*
  * Free a state id
  */
 __be32
diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
index 5da6874..c92c4d2 100644
--- a/fs/nfsd/nfs4xdr.c
+++ b/fs/nfsd/nfs4xdr.c
@@ -44,6 +44,7 @@
 #include <linux/namei.h>
 #include <linux/statfs.h>
 #include <linux/utsname.h>
+#include <linux/pagemap.h>
 #include <linux/sunrpc/svcauth_gss.h>
 
 #include "idmap.h"
@@ -1274,6 +1275,98 @@ nfsd4_decode_sequence(struct nfsd4_compoundargs *argp,
 	DECODE_TAIL;
 }
 
+static void
+nfsd4_free_ts_pagearray(struct nfsd4_ts_pagearray *array)
+{
+	unsigned int i;
+	struct page *page;
+	for (i = 0; i < array->num_pages; i++) {
+		kunmap(array->items[i].page);
+		put_page(array->items[i].page);
+	}
+	page = array->page_ptr;
+	put_page(page);
+}
+
+static struct nfsd4_ts_pagearray *
+nfsd4_alloc_ts_pagearray(struct nfsd4_test_stateid *test_stateid, unsigned int item_size)
+{
+	unsigned int num_pages;
+	struct nfsd4_ts_pagearray *array;
+	struct page *page;
+	int i = 0;
+
+	page = alloc_page(GFP_KERNEL);
+	if (page == NULL)
+		goto out_nomem;
+
+	array = kmap(page);
+	array->num_pages = 0;
+	array->items_per_page = PAGE_SIZE / item_size;
+	array->page_ptr  = page;
+
+	num_pages = ((test_stateid->ts_num_ids * item_size) / PAGE_SIZE) + 1;
+
+	for (i = 0; i < num_pages; i++) {
+		page = alloc_page(GFP_KERNEL);
+		if (page == NULL)
+			goto out_freepages;
+		array->items[i].page = page;
+		array->items[i].first_item = kmap(page);
+		array->num_pages += 1;
+	}
+	return array;
+
+out_freepages:
+	nfsd4_free_ts_pagearray(array);
+out_nomem:
+	return ERR_PTR(-ENOMEM);
+}
+
+static __be32
+nfsd4_decode_test_stateid(struct nfsd4_compoundargs *argp, struct nfsd4_test_stateid *test_stateid)
+{
+	stateid_t * si = NULL;
+	unsigned int cur_page = 0;
+	struct page *page;
+	int i;
+
+	DECODE_HEAD;
+	READ_BUF(4);
+	READ32(test_stateid->ts_num_ids);
+
+	test_stateid->ts_stateids = nfsd4_alloc_ts_pagearray(test_stateid, sizeof(stateid_t));
+	if (IS_ERR(test_stateid->ts_stateids))
+		return PTR_ERR(test_stateid->ts_stateids);
+
+	test_stateid->ts_valid = nfsd4_alloc_ts_pagearray(test_stateid, sizeof(int));
+	if (IS_ERR(test_stateid->ts_valid)) {
+		status = PTR_ERR(test_stateid->ts_valid);
+		goto out_free_pages;
+	}
+
+	/* Read in a list of stateids provided by the client */
+	for (i = 0; i < test_stateid->ts_num_ids; i++) {
+		if ((i % test_stateid->ts_stateids->items_per_page) == 0) {
+			si = test_stateid->ts_stateids->items[cur_page].first_item;
+			cur_page++;
+		}
+		nfsd4_decode_stateid(argp, si);
+		si++;
+	}
+	kunmap(page);
+	status = 0;
+out:
+	return status;
+xdr_error:
+	dprintk("NFSD: xdr error (%s:%d)\n", __FILE__, __LINE__);
+	status = nfserr_bad_xdr;
+	nfsd4_free_ts_pagearray(test_stateid->ts_valid);
+out_free_pages:
+	nfsd4_free_ts_pagearray(test_stateid->ts_stateids);
+	goto out;
+}
+
 static __be32 nfsd4_decode_reclaim_complete(struct nfsd4_compoundargs *argp, struct nfsd4_reclaim_complete *rc)
 {
 	DECODE_HEAD;
@@ -1393,7 +1486,7 @@ static nfsd4_dec nfsd41_dec_ops[] = {
 	[OP_SECINFO_NO_NAME]	= (nfsd4_dec)nfsd4_decode_secinfo_no_name,
 	[OP_SEQUENCE]		= (nfsd4_dec)nfsd4_decode_sequence,
 	[OP_SET_SSV]		= (nfsd4_dec)nfsd4_decode_notsupp,
-	[OP_TEST_STATEID]	= (nfsd4_dec)nfsd4_decode_notsupp,
+	[OP_TEST_STATEID]	= (nfsd4_dec)nfsd4_decode_test_stateid,
 	[OP_WANT_DELEGATION]	= (nfsd4_dec)nfsd4_decode_notsupp,
 	[OP_DESTROY_CLIENTID]	= (nfsd4_dec)nfsd4_decode_notsupp,
 	[OP_RECLAIM_COMPLETE]	= (nfsd4_dec)nfsd4_decode_reclaim_complete,
@@ -3166,6 +3259,37 @@ nfsd4_encode_sequence(struct nfsd4_compoundres *resp, int nfserr,
 	return 0;
 }
 
+__be32
+nfsd4_encode_test_stateid(struct nfsd4_compoundres *resp, int nfserr,
+			  struct nfsd4_test_stateid *test_stateid)
+{
+	__be32 *p;
+	unsigned int i;
+	unsigned int cur_page = 0;
+	int *valid = NULL;
+
+	if (!nfserr) {
+		RESERVE_SPACE(4);
+		WRITE32(test_stateid->ts_num_ids);
+		ADJUST_ARGS();
+
+		for (i = 0; i < test_stateid->ts_num_ids; i++) {
+			if ((i % test_stateid->ts_valid->items_per_page) == 0) {
+				valid = (int *)test_stateid->ts_valid->items[cur_page].first_item;
+				cur_page++;
+			}
+			RESERVE_SPACE(4);
+			WRITE32(*valid);
+			ADJUST_ARGS();
+			valid++;
+		}
+	}
+
+	nfsd4_free_ts_pagearray(test_stateid->ts_valid);
+	nfsd4_free_ts_pagearray(test_stateid->ts_stateids);
+	return nfserr;
+}
+
 static __be32
 nfsd4_encode_noop(struct nfsd4_compoundres *resp, __be32 nfserr, void *p)
 {
@@ -3234,7 +3358,7 @@ static nfsd4_enc nfsd4_enc_ops[] = {
 	[OP_SECINFO_NO_NAME]	= (nfsd4_enc)nfsd4_encode_secinfo_no_name,
 	[OP_SEQUENCE]		= (nfsd4_enc)nfsd4_encode_sequence,
 	[OP_SET_SSV]		= (nfsd4_enc)nfsd4_encode_noop,
-	[OP_TEST_STATEID]	= (nfsd4_enc)nfsd4_encode_noop,
+	[OP_TEST_STATEID]	= (nfsd4_enc)nfsd4_encode_test_stateid,
 	[OP_WANT_DELEGATION]	= (nfsd4_enc)nfsd4_encode_noop,
 	[OP_DESTROY_CLIENTID]	= (nfsd4_enc)nfsd4_encode_noop,
 	[OP_RECLAIM_COMPLETE]	= (nfsd4_enc)nfsd4_encode_noop,
diff --git a/fs/nfsd/xdr4.h b/fs/nfsd/xdr4.h
index ed1784d..0455f55 100644
--- a/fs/nfsd/xdr4.h
+++ b/fs/nfsd/xdr4.h
@@ -342,6 +342,24 @@ struct nfsd4_setclientid_confirm {
 	nfs4_verifier	sc_confirm;
 };
 
+struct nfsd4_ts_pagearray_item {
+	struct page *page;
+	void *first_item;
+};
+
+struct nfsd4_ts_pagearray {
+	unsigned int num_pages;
+	unsigned int items_per_page;
+	struct page *page_ptr;
+	struct nfsd4_ts_pagearray_item items[0];
+};
+
+struct nfsd4_test_stateid {
+	__be32		ts_num_ids;             /* request */
+	struct nfsd4_ts_pagearray *ts_stateids; /* request */
+	struct nfsd4_ts_pagearray *ts_valid;    /* response */
+};
+
 struct nfsd4_free_stateid {
 	stateid_t	fr_stateid;         /* request */
 	__be32		fr_status;          /* response */
@@ -437,6 +455,7 @@ struct nfsd4_op {
 		struct nfsd4_destroy_session	destroy_session;
 		struct nfsd4_sequence		sequence;
 		struct nfsd4_reclaim_complete	reclaim_complete;
+		struct nfsd4_test_stateid	test_stateid;
 		struct nfsd4_free_stateid	free_stateid;
 	} u;
 	struct nfs4_replay *			replay;
@@ -570,6 +589,8 @@ extern __be32 nfsd4_delegreturn(struct svc_rqst *rqstp,
 		struct nfsd4_compound_state *, struct nfsd4_delegreturn *dr);
 extern __be32 nfsd4_renew(struct svc_rqst *rqstp,
 			  struct nfsd4_compound_state *, clientid_t *clid);
+extern __be32 nfsd4_test_stateid(struct svc_rqst *rqstp,
+		struct nfsd4_compound_state *, struct nfsd4_test_stateid *test_stateid);
 extern __be32 nfsd4_free_stateid(struct svc_rqst *rqstp,
 		struct nfsd4_compound_state *, struct nfsd4_free_stateid *free_stateid);
 #endif
