diff --git a/README.md b/README.md
index 26784f8..329a495 100644
--- a/README.md
+++ b/README.md
@@ -114,6 +114,13 @@ the tests:
 
 ## Running the Tests
 
+The SELinux testsuite is primarily a set of regression tests for the SELinux
+kernel, however it is possible to include the testing of core userspace
+services such as library functions or utilities. To install any relevant
+tests, the following must be run first:
+
+	# export TEST_USERSPACE=y
+
 Create a shell with the `unconfined_r` or `sysadm_r` role and the Linux
 superuser identity, e.g.:
 
diff --git a/policy/Makefile b/policy/Makefile
index 305b572..9c7d173 100644
--- a/policy/Makefile
+++ b/policy/Makefile
@@ -3,6 +3,7 @@ POLDEV ?= /usr/share/selinux/devel
 SEMODULE = /usr/sbin/semodule
 CHECKPOLICY = /usr/bin/checkpolicy
 CHECKMODULE = /usr/bin/checkmodule
+INCLUDEDIR ?= /usr/include
 
 DISTRO=$(shell ../tests/os_detect)
 RHEL_VERS=$(shell echo $(DISTRO) | sed 's/RHEL//')
@@ -79,6 +80,13 @@ ifeq (x$(DISTRO),$(filter x$(DISTRO), xRHEL6))
 TARGETS:=$(filter-out test_ibpkey.te, $(TARGETS))
 endif
 
+# Add any userspace test policy
+ifeq ($(TEST_USERSPACE),y)
+    ifeq ($(shell grep -q selabel_get_digests_all_partial_matches $(INCLUDEDIR)/selinux/label.h && echo true),true)
+        TARGETS += test_restorecon.te
+    endif
+endif
+
 ifeq (x$(DISTRO),$(filter x$(DISTRO),xRHEL4 xRHEL5))
 	BUILD_TARGET := build_rhel
 	LOAD_TARGET := load_rhel
diff --git a/policy/test_restorecon.te b/policy/test_restorecon.te
new file mode 100644
index 0000000..e3ffae5
--- /dev/null
+++ b/policy/test_restorecon.te
@@ -0,0 +1,51 @@
+#################################
+#
+# Policy for testing restorecon
+#
+
+require {
+	attribute file_type;
+	type setfiles_exec_t;
+}
+
+attribute restorecon_domain;
+
+type test_restorecon_file_t;
+files_type(test_restorecon_file_t)
+type in_dir_t;
+files_type(in_dir_t)
+type out_dir_t;
+files_type(out_dir_t)
+type in_file_t;
+files_type(in_file_t)
+type out_file_t;
+files_type(out_file_t)
+
+# Domain for restorecon.
+type test_restorecon_t;
+files_type(test_restorecon_t)
+
+domain_type(test_restorecon_t)
+unconfined_runs_test(test_restorecon_t)
+typeattribute test_restorecon_t testdomain;
+typeattribute test_restorecon_t restorecon_domain;
+
+allow test_restorecon_t self:capability sys_admin;
+allow test_restorecon_t test_file_t:file relabelfrom;
+allow test_restorecon_t file_type:dir { relabel_dir_perms manage_dir_perms };
+allow test_restorecon_t file_type:file { rw_file_perms execute relabelto relabelfrom };
+allow_map(test_restorecon_t, file_type, file)
+corecmd_bin_entry_type(test_restorecon_t)
+
+# Allow these for statfs(2) if /tmp = TMPFS_MAGIC test
+allow test_restorecon_t tmpfs_t:filesystem getattr;
+allow test_restorecon_t user_tmp_t:sock_file getattr;
+# and this to add the root test directory
+allow test_restorecon_t fs_t:filesystem getattr;
+
+# Allow restorecon(8) to be used instead of the test program
+allow test_restorecon_t setfiles_exec_t:file entrypoint;
+
+# Allow all of these domains to be entered from sysadm domain
+miscfiles_domain_entry_test_files(restorecon_domain)
+userdom_sysadm_entry_spec_domtrans_to(restorecon_domain)
diff --git a/tests/Makefile b/tests/Makefile
index 63aa325..37ed6af 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -50,6 +50,13 @@ ifeq ($(shell grep "^SELINUX_INFINIBAND_PKEY_TEST=" infiniband_pkey/ibpkey_test.
 SUBDIRS += infiniband_pkey
 endif
 
+# Keep userspace tests last
+ifeq ($(TEST_USERSPACE),y)
+    ifeq ($(shell grep -q selabel_get_digests_all_partial_matches $(INCLUDEDIR)/selinux/label.h && echo true),true)
+        SUBDIRS += restorecon
+    endif
+endif
+
 ifeq ($(DISTRO),RHEL4)
     SUBDIRS:=$(filter-out bounds dyntrace dyntrans inet_socket mmap nnp_nosuid overlay unix_socket, $(SUBDIRS))
 endif
diff --git a/tests/restorecon/.gitignore b/tests/restorecon/.gitignore
new file mode 100644
index 0000000..7a8717a
--- /dev/null
+++ b/tests/restorecon/.gitignore
@@ -0,0 +1,3 @@
+restorecon
+get_all_digests
+check_fs
diff --git a/tests/restorecon/Makefile b/tests/restorecon/Makefile
new file mode 100644
index 0000000..2ea2282
--- /dev/null
+++ b/tests/restorecon/Makefile
@@ -0,0 +1,7 @@
+TARGETS = restorecon get_all_digests check_fs
+LDLIBS += -lselinux
+
+all: $(TARGETS)
+
+clean:
+	rm -f $(TARGETS)
diff --git a/tests/restorecon/check_fs.c b/tests/restorecon/check_fs.c
new file mode 100644
index 0000000..5a3c7b1
--- /dev/null
+++ b/tests/restorecon/check_fs.c
@@ -0,0 +1,69 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <sys/statfs.h>
+#include <linux/magic.h>
+
+static void usage(char *progname)
+{
+	fprintf(stderr,
+		"usage:  %s [-v] path\n"
+		"Where:\n\t"
+		"-v  Display filesystem f_type magic number.\n\t"
+		"path  Path to check fs type.\n\n"
+		"Returns 1=RAMFS_MAGIC, 2=TMPFS_MAGIC, 3=SYSFS_MAGIC\n", progname);
+	exit(-1);
+}
+
+int main(int argc, char **argv)
+{
+	int opt, rc;
+	bool verbose = false;
+	struct statfs sfsb;
+
+	while ((opt = getopt(argc, argv, "v")) > 0) {
+		switch (opt) {
+		case 'v':
+			verbose = true;
+			break;
+		default:
+			usage(argv[0]);
+		}
+	}
+
+	if (optind >= argc) {
+		fprintf(stderr, "No pathname specified\n");
+		return -1;
+	}
+
+	rc = statfs(argv[optind], &sfsb);
+	if (rc < 0) {
+		fprintf(stderr, "Get filesystem statistics ERROR: %s\n",
+			strerror(errno));
+		return rc;
+	}
+
+	switch (sfsb.f_type) {
+	case RAMFS_MAGIC:
+		if (verbose)
+			printf("RAMFS_MAGIC\n");
+		return 1;
+	case TMPFS_MAGIC:
+		if (verbose)
+			printf("TMPFS_MAGIC\n");
+		return 2;
+	case SYSFS_MAGIC:
+		if (verbose)
+			printf("SYSFS_MAGIC\n");
+		return 3;
+	default:
+		if (verbose)
+			printf("sfsb.f_type: 0x%lx\n", sfsb.f_type);
+		return 0;
+	}
+
+	return rc;
+}
diff --git a/tests/restorecon/get_all_digests.c b/tests/restorecon/get_all_digests.c
new file mode 100644
index 0000000..4a4340f
--- /dev/null
+++ b/tests/restorecon/get_all_digests.c
@@ -0,0 +1,175 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <fts.h>
+#include <selinux/selinux.h>
+#include <selinux/label.h>
+
+#define RESTORECON_PARTIAL_MATCH_DIGEST  "security.sehash"
+
+static void usage(char *progname)
+{
+	fprintf(stderr,
+		"usage:  %s [-vr] path\n\n"
+		"Where:\n\t"
+		"-v  Display information.\n\t"
+		"-r  Recursively descend directories.\n\t"
+		"path  Path to check current SHA1 digest against file_contexts entries.\n\n"
+		"This will check the directory selinux.sehash SHA1 digest for "
+		"<path> against\na newly generated digest based on the "
+		"file_context entries for that node\n(using the regx, mode "
+		"and path entries).\n", progname);
+	exit(1);
+}
+
+static int get_digests(struct selabel_handle *hnd, bool verbose, char *path)
+{
+	int rc = 0;
+	size_t i, digest_len = 0;
+	bool status;
+	uint8_t *xattr_digest = NULL;
+	uint8_t *calculated_digest = NULL;
+	char *sha1_buf = NULL;
+
+	status = selabel_get_digests_all_partial_matches(hnd, path,
+							 &calculated_digest,
+							 &xattr_digest,
+							 &digest_len);
+
+	sha1_buf = calloc(1, digest_len * 2 + 1);
+	if (!sha1_buf) {
+		fprintf(stderr, "Could not calloc buffer ERROR: %s\n",
+			strerror(errno));
+		rc = -1;
+		goto out;
+	}
+
+	/* rc = 0 NO MATCH, rc = 1 MATCH, rc = 2 NO calculated_digest
+	 * rc = 4 NO xattr_digest, rc = 6 NO digests
+	 */
+	if (status) { /* They match */
+		if (verbose) {
+			printf("xattr and file_contexts SHA1 digests match for: %s\n",
+			       path);
+
+			if (calculated_digest) {
+				for (i = 0; i < digest_len; i++)
+					sprintf((&sha1_buf[i * 2]), "%02x",
+						calculated_digest[i]);
+				printf("SHA1 digest: %s\n", sha1_buf);
+			}
+		}
+
+		rc = 1;
+		goto out;
+	} else {
+		if (!calculated_digest) {
+			rc = 2;
+			if (verbose) {
+				printf("No SHA1 digest available for: %s\n", path);
+				printf("as file_context entry is \"<<none>>\"\n");
+			}
+		}
+
+		if (calculated_digest && verbose) {
+			printf("The file_context entries for: %s\n", path);
+
+			for (i = 0; i < digest_len; i++)
+				sprintf((&sha1_buf[i * 2]), "%02x", calculated_digest[i]);
+			printf("generated SHA1 digest: %s\n", sha1_buf);
+		}
+		if (!xattr_digest) {
+			rc = rc | 4;
+			if (verbose)
+				printf("however there is no selinux.sehash xattr entry.\n");
+			else
+				goto out;
+
+		} else if (verbose) {
+			printf("however it does NOT match the current entry of:\n");
+			for (i = 0; i < digest_len; i++)
+				sprintf((&sha1_buf[i * 2]), "%02x", xattr_digest[i]);
+			printf("%s\n", sha1_buf);
+		}
+	}
+
+	free(sha1_buf);
+out:
+	free(xattr_digest);
+	free(calculated_digest);
+	return rc;
+}
+
+int main(int argc, char **argv)
+{
+	int opt, fts_flags, status;
+	bool verbose = false, recurse = false;
+	FTS *fts;
+	FTSENT *ftsent;
+	char *paths[2] = { NULL, NULL };
+	struct selabel_handle *hnd;
+
+	if (argc < 2)
+		usage(argv[0]);
+
+	while ((opt = getopt(argc, argv, "vr")) > 0) {
+		switch (opt) {
+		case 'v':
+			verbose = true;
+			break;
+		case 'r':
+			recurse = true;
+			break;
+		default:
+			usage(argv[0]);
+		}
+	}
+
+	if (optind >= argc) {
+		fprintf(stderr, "No pathname specified\n");
+		exit(-1);
+	}
+	paths[0] = argv[optind];
+
+	hnd = selabel_open(SELABEL_CTX_FILE, NULL, 0);
+	if (!hnd) {
+		fprintf(stderr, "ERROR: selabel_open - Could not obtain handle.\n");
+		return -1;
+	}
+
+	fts_flags = FTS_PHYSICAL | FTS_NOCHDIR;
+	fts = fts_open(paths, fts_flags, NULL);
+	if (!fts) {
+		printf("fts error on %s: %s\n",
+		       paths[0], strerror(errno));
+		return -1;
+	}
+
+	while ((ftsent = fts_read(fts)) != NULL) {
+		switch (ftsent->fts_info) {
+		case FTS_D:
+			/* If recurse = TRUE, then 'status' will reflect the
+			 * last path match with 0 = NO MATCH, 1 = MATCH,
+			 * 2 = NO calculated_digest, 4 = NO xattr_digest and
+			 * 6 = NO digests.
+			 */
+			status = get_digests(hnd, verbose, ftsent->fts_path);
+			if (status < 0)
+				goto out;
+			break;
+		default:
+			break;
+		}
+
+		if (!recurse)
+			break;
+	}
+
+out:
+	(void) fts_close(fts);
+	(void) selabel_close(hnd);
+	return status;
+}
diff --git a/tests/restorecon/restorecon.c b/tests/restorecon/restorecon.c
new file mode 100644
index 0000000..7b3bfaf
--- /dev/null
+++ b/tests/restorecon/restorecon.c
@@ -0,0 +1,74 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <selinux/selinux.h>
+#include <selinux/label.h>
+#include <selinux/restorecon.h>
+
+static void usage(char *progname)
+{
+	fprintf(stderr,
+		"usage:  %s [-IDrv] path\n\n"
+		"Where:\n\t"
+		"-I  Set SELINUX_RESTORECON_IGNORE_DIGEST\n\t"
+		"-D  Set digests\n\t"
+		"-r  Recursively descend directories.\n\t"
+		"-v  Display information.\n\t"
+		"path  Path of file or directory to check.\n", progname);
+	exit(-1);
+}
+
+int main(int argc, char **argv)
+{
+	int opt, rc, flags = SELINUX_RESTORECON_SKIP_DIGEST;
+
+	struct selabel_handle *hnd;
+
+	if (argc < 2)
+		usage(argv[0]);
+
+	while ((opt = getopt(argc, argv, "IDrv")) > 0) {
+		switch (opt) {
+		case 'I':
+			flags |= SELINUX_RESTORECON_IGNORE_DIGEST;
+			break;
+		case 'D':
+			flags ^= SELINUX_RESTORECON_SKIP_DIGEST;
+			break;
+		case 'r':
+			flags |= SELINUX_RESTORECON_RECURSE;
+			break;
+		case 'v':
+			flags |= SELINUX_RESTORECON_VERBOSE;
+			break;
+		default:
+			usage(argv[0]);
+		}
+	}
+
+	if (optind >= argc) {
+		fprintf(stderr, "No pathname specified\n");
+		return -1;
+	}
+
+	hnd = selabel_open(SELABEL_CTX_FILE, NULL, 0);
+	if (!hnd) {
+		fprintf(stderr, "ERROR: selabel_open - Could not obtain handle.\n");
+		return -1;
+	}
+
+	/* Use own handle */
+	selinux_restorecon_set_sehandle(hnd);
+
+	rc = selinux_restorecon(argv[optind], flags);
+	if (rc < 0)
+		fprintf(stderr, "selinux_restorecon ERROR: %s\n",
+			strerror(errno));
+
+	selabel_close(hnd);
+	return rc;
+}
+
diff --git a/tests/restorecon/test b/tests/restorecon/test
new file mode 100755
index 0000000..648c794
--- /dev/null
+++ b/tests/restorecon/test
@@ -0,0 +1,147 @@
+#!/usr/bin/perl
+use Test::More;
+
+# Options: -v = Verbose, -p <path_to_restorecon>
+# NOTE: If using the -p option to use a different version of restorecon,
+# ensure they are labeled correctly before use. This can be achieved by:
+#    chcon -h -t bin_t .../sbin/restorecon
+#    chcon -h -t setfiles_exec_t .../sbin/setfiles
+# The test_restorecon.te policy has rules for this labeling.
+
+BEGIN {
+    $basedir = $0;
+    $basedir =~ s|(.*)/[^/]*|$1|;
+
+    $v      = " ";
+    $bindir = $basedir;
+    $i      = 0;
+    foreach $arg (@ARGV) {
+        if ( $arg eq "-v" ) {
+            $v = $arg;
+        }
+        elsif ( $arg eq "-p" ) {
+            $bindir = $ARGV[ $i + 1 ];
+            if ( not -e "$bindir/restorecon" ) {
+                plan skip_all => "restorecon not found";
+            }
+        }
+        $i++;
+    }
+
+    # check if /tmp is really type tmpfs (TMPFS_MAGIC).
+    $test_tmpfs = 0;
+    $result     = system("$basedir/check_fs $v /tmp 2>/dev/null");
+    if ( $result >> 8 eq 2 ) {
+        $test_tmpfs = 1;
+        plan tests => 11;
+    }
+    else {
+        plan tests => 10;
+    }
+}
+
+print "Using \"restorecon\" from $bindir\n";
+
+# Make sure test directory removed then generate new. Using a root dir to test
+# libselinux: Ignore the stem when looking up all matches in file context
+print "Generating test directories\n";
+system("rm -rf /restore_test");
+system("mkdir -p /restore_test/in_dir");
+system("mkdir -p /restore_test/out_dir");
+
+# Using semanage is much quicker than using semodule to build fc entries.
+print "semanage adding file context entries\n";
+system("semanage fcontext -a -t test_file_t -f d /restore_test");
+system("semanage fcontext -a -t in_dir_t -f d /restore_test/in_dir");
+system("semanage fcontext -a -t out_dir_t -f d /restore_test/out_dir");
+
+print "Add files to the directories\n";
+system("touch /restore_test/out_dir/out_file1");
+system("touch /restore_test/in_dir/in_file1");
+
+print "Check there are no xattr digest entries\n";
+$result = system(
+    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test");
+ok( $result >> 8 eq 4 );
+
+print "Run restorecon to add digest entries, then check they match\n";
+system("runcon -t test_restorecon_t $bindir/restorecon -rD $v /restore_test");
+$result = system(
+    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test");
+ok( $result >> 8 eq 1 );
+
+print "Add new file context entries, then check digests do not match\n";
+system("semanage fcontext -a -t in_file_t -f f \"/restore_test/in_dir(/.*)?\"");
+system(
+    "semanage fcontext -a -t out_file_t -f f \"/restore_test/out_dir(/.*)?\"");
+$result = system(
+    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test");
+ok( $result eq 0 );
+
+print "Now fix with restorecon and check digests match\n";
+system("runcon -t test_restorecon_t $bindir/restorecon -rD $v /restore_test");
+$result = system(
+    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test");
+ok( $result >> 8 eq 1 );
+
+print "Remove sehash entry on /restore_test/out_dir then check if removed\n";
+system(
+"runcon -t test_restorecon_t setfattr -x security.sehash /restore_test/out_dir"
+);
+$result = system(
+"runcon -t test_restorecon_t $basedir/get_all_digests $v /restore_test/out_dir"
+);
+ok( $result >> 8 eq 4 );
+
+print "Run restorecon with SELINUX_RESTORECON_IGNORE_DIGEST = TRUE,\n";
+print "then check that digests match\n";
+system("runcon -t test_restorecon_t $bindir/restorecon -Ir $v /restore_test");
+$result = system(
+    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test");
+ok( $result >> 8 eq 1 );
+
+print "Remove some file context entries, then check digests do not match\n";
+system("semanage fcontext -d -t in_dir_t -f d /restore_test/in_dir");
+system("semanage fcontext -d -t out_dir_t -f d /restore_test/out_dir");
+system("semanage fcontext -d -t in_file_t -f f \"/restore_test/in_dir(/.*)?\"");
+$result = system(
+    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test");
+ok( $result eq 0 );
+
+print "Run restorecon with SELINUX_RESTORECON_SKIP_DIGEST = TRUE,\n";
+print "then check that digests still do not match as updates were skipped\n";
+system("runcon -t test_restorecon_t $bindir/restorecon -r $v /restore_test");
+$result = system(
+    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test");
+ok( $result eq 0 );
+
+print "Run restorecon with SELINUX_RESTORECON_SKIP_DIGEST = FALSE,\n";
+print "then check that digests match\n";
+system("runcon -t test_restorecon_t $bindir/restorecon -rD $v /restore_test");
+$result = system(
+    "runcon -t test_restorecon_t $basedir/get_all_digests -r $v /restore_test");
+ok( $result >> 8 eq 1 );
+
+system(
+    "semanage fcontext -d -t in_file_t -f f \"/restore_test/out_dir(/.*)?\"");
+system("semanage fcontext -d -t test_file_t -f d /restore_test");
+system("rm -rf /restore_test");
+
+print "Run restorecon on /sys with SELINUX_RESTORECON_SKIP_DIGEST = FALSE,\n";
+print "then check the digests are skipped as /sys is SYSFS_MAGIC.\n";
+system("runcon -t test_restorecon_t $bindir/restorecon -rD $v /sys/fs/selinux");
+$result = system(
+    "runcon -t test_restorecon_t $basedir/get_all_digests $v /sys/fs/selinux");
+ok( $result >> 8 eq 4 );
+
+if ($test_tmpfs) {
+    print
+      "Run restorecon on /tmp with SELINUX_RESTORECON_SKIP_DIGEST = FALSE,\n";
+    print "then check the digests are skipped as /tmp is TMPFS_MAGIC\n";
+    system("runcon -t test_restorecon_t $bindir/restorecon -rD $v /tmp");
+    $result =
+      system("runcon -t test_restorecon_t $basedir/get_all_digests $v /tmp");
+    ok( $result >> 8 eq 4 );
+}
+
+exit;
