summaryrefslogtreecommitdiffstats
path: root/libsefs/tests
diff options
context:
space:
mode:
Diffstat (limited to 'libsefs/tests')
-rw-r--r--libsefs/tests/Makefile.am17
-rw-r--r--libsefs/tests/attic/fuse_non_mls.c195
-rwxr-xr-xlibsefs/tests/attic/launch-libsefs-tests.sh12
-rw-r--r--libsefs/tests/fcfile-tests.cc333
-rw-r--r--libsefs/tests/fcfile-tests.hh35
-rw-r--r--libsefs/tests/file_contexts.broken3
-rw-r--r--libsefs/tests/file_contexts.confed24
-rw-r--r--libsefs/tests/file_contexts.union16
-rw-r--r--libsefs/tests/libsefs-tests.cc52
9 files changed, 687 insertions, 0 deletions
diff --git a/libsefs/tests/Makefile.am b/libsefs/tests/Makefile.am
new file mode 100644
index 0000000..50f7af0
--- /dev/null
+++ b/libsefs/tests/Makefile.am
@@ -0,0 +1,17 @@
+TESTS = libsefs-tests
+check_PROGRAMS = libsefs-tests
+
+libsefs_tests_SOURCES = \
+ fcfile-tests.cc fcfile-tests.hh \
+ libsefs-tests.cc
+
+EXTRA_DIST = file_contexts.confed file_contexts.union file_contexts.broken
+
+AM_CXXFLAGS = @DEBUGCFLAGS@ @WARNCFLAGS@ @PROFILECFLAGS@ @SELINUX_CFLAGS@ \
+ @QPOL_CFLAGS@ @APOL_CFLAGS@ @SEFS_CFLAGS@ -DSRCDIR="\"$(srcdir)\""
+
+AM_LDFLAGS = @DEBUGLDFLAGS@ @WARNLDFLAGS@ @PROFILELDFLAGS@
+
+LDADD = @SELINUX_LIB_FLAG@ @SEFS_LIB_FLAG@ @APOL_LIB_FLAG@ @QPOL_LIB_FLAG@ @CUNIT_LIB_FLAG@
+
+libsefs_tests_DEPENDENCIES = ../src/libsefs.so \ No newline at end of file
diff --git a/libsefs/tests/attic/fuse_non_mls.c b/libsefs/tests/attic/fuse_non_mls.c
new file mode 100644
index 0000000..30b3df4
--- /dev/null
+++ b/libsefs/tests/attic/fuse_non_mls.c
@@ -0,0 +1,195 @@
+/*
+ * The approach described below does not actually work. Apparantly,
+ * SELinux will assign a context based upond the underlying policy
+ * (typically from a fs_use statement); the operating system will not
+ * invoke this file's fuse_getxattr() function at all. Thus it is
+ * not possible to use FUSE to create a virtual filesystem with
+ * arbitrary file contexts.
+ */
+
+/**
+ * @file
+ *
+ * Use the FUSE (filesystem in userspace) to create a virtual
+ * filesystem for libsefs tests. This particular filesystem will not
+ * contain MLS contexts.
+ *
+ * @author Jeremy A. Mowery jmowery@tresys.com
+ * @author Jason Tang jtang@tresys.com
+ *
+ * Copyright (C) 2007 Tresys Technology, LLC
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#define FUSE_USE_VERSION 25
+
+#define XATTR_NAME_SELINUX "security.selinux"
+
+#include <apol/bst.h>
+#include <fuse.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+/*************** definition of the virtual filesystem ***************/
+
+static apol_bst_t *bst = NULL;
+
+struct fuse_entry
+{
+ const char *path;
+ mode_t mode;
+ const char *context;
+};
+
+static struct fuse_entry fs[] = {
+ {"/", S_IFDIR, "user_r:object_r:system_t"},
+ {"/foo", S_IFREG, "user_r:object_r:system_t"},
+ {NULL, 0, NULL}
+};
+
+static int fuse_comp(const void *a, const void *b, void *data __attribute__ ((unused)))
+{
+ const struct fuse_entry *f1 = (const struct fuse_entry *)a;
+ const struct fuse_entry *f2 = (const struct fuse_entry *)b;
+ return strcmp(f1->path, f2->path);
+}
+
+/*************** required fuse functions ***************/
+
+static int fuse_getattr(const char *path, struct stat *stbuf)
+{
+ struct fuse_entry key = { path, 0, NULL };
+ struct fuse_entry *e;
+ memset(stbuf, 0, sizeof(*stbuf));
+ if (apol_bst_get_element(bst, &key, NULL, (void **)&e) < 0) {
+ return -ENOENT;
+ }
+ if (e->mode == S_IFDIR) {
+ stbuf->st_mode = S_IFDIR | 0755;
+ stbuf->st_nlink = 2;
+ } else {
+ stbuf->st_mode = e->mode | 0444;
+ stbuf->st_nlink = 1;
+ }
+ return 0;
+}
+
+static int fuse_getxattr(const char *path, const char *attrib_name, char *buf, size_t buflen)
+{
+ struct fuse_entry key = { path, 0, NULL };
+ struct fuse_entry *e;
+ if (apol_bst_get_element(bst, &key, NULL, (void **)&e) < 0) {
+ return -ENOENT;
+ }
+ if (strcmp(attrib_name, XATTR_NAME_SELINUX) != 0) {
+ return -ENOSYS;
+ }
+ strncpy(buf, e->context, buflen);
+ return strlen(e->context) + 1;
+}
+
+static int fuse_open(const char *path, struct fuse_file_info *fi __attribute__ ((unused)))
+{
+ struct fuse_entry key = { path, 0, NULL };
+ struct fuse_entry *e;
+ if (apol_bst_get_element(bst, &key, NULL, (void **)&e) < 0) {
+ return -ENOENT;
+ }
+
+ return -EACCES;
+}
+
+static int fuse_read(const char *path, char *buf __attribute__ ((unused)), size_t size, off_t offset __attribute__ ((unused)),
+ struct fuse_file_info *fi __attribute__ ((unused)))
+{
+ struct fuse_entry key = { path, 0, NULL };
+ struct fuse_entry *e;
+ if (apol_bst_get_element(bst, &key, NULL, (void **)&e) < 0) {
+ return -ENOENT;
+ }
+ return size;
+}
+
+struct stem_data
+{
+ const char *stem;
+ void *buf;
+ fuse_fill_dir_t filler;
+};
+
+static int fuse_stem_match(void *a, void *data)
+{
+ const struct fuse_entry *e = (const struct fuse_entry *)a;
+ struct stem_data *sd = (struct stem_data *)data;
+
+ size_t e_len = strlen(e->path);
+ size_t stem_len = strlen(sd->stem);
+ if (e_len <= stem_len) {
+ /* entry's path is too longer than the requested path */
+ return 0;
+ }
+ if (strncmp(e->path, sd->stem, stem_len) != 0) {
+ /* stem is not the beginning of entry's path */
+ return 0;
+ }
+ const char *file = e->path + 1;
+ if (strchr(file, '/') != NULL) {
+ /* member of a subdirectory of stem */
+ return 0;
+ }
+ return -sd->filler(sd->buf, file, NULL, 0);
+}
+
+static int fuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+ off_t offset __attribute__ ((unused)), struct fuse_file_info *fi __attribute__ ((unused)))
+{
+ struct fuse_entry key = { path, 0, NULL };
+ struct fuse_entry *e;
+ if (apol_bst_get_element(bst, &key, NULL, (void **)&e) < 0) {
+ return -ENOENT;
+ }
+
+ filler(buf, ".", NULL, 0);
+ filler(buf, "..", NULL, 0);
+ struct stem_data sd = { path, buf, filler };
+ return -apol_bst_inorder_map(bst, fuse_stem_match, &sd);
+}
+
+int main(int argc, char *argv[])
+{
+ if ((bst = apol_bst_create(fuse_comp, NULL)) < 0) {
+ return 1;
+ }
+ for (size_t i = 0; fs[i].path != NULL; i++) {
+ if (apol_bst_insert(bst, &fs[i], NULL) < 0) {
+ return 1;
+ }
+ }
+
+ struct fuse_operations non_mls_oper = {
+ .getattr = fuse_getattr,
+ .getxattr = fuse_getxattr,
+ .open = fuse_open,
+ .read = fuse_read,
+ .readdir = fuse_readdir,
+ };
+ return fuse_main(argc, argv, &non_mls_oper);
+}
diff --git a/libsefs/tests/attic/launch-libsefs-tests.sh b/libsefs/tests/attic/launch-libsefs-tests.sh
new file mode 100755
index 0000000..94bc873
--- /dev/null
+++ b/libsefs/tests/attic/launch-libsefs-tests.sh
@@ -0,0 +1,12 @@
+# Mount the virtual filesystems, execute the the real test, then
+# unmount those filesystems.
+
+mkdir -p non-mls
+mkdir -p mls
+./fuse_non_mls non-mls
+./libsefs-tests
+result=$?
+fusermount -u non-mls
+rmdir non-mls
+rmdir mls
+exit ${result}
diff --git a/libsefs/tests/fcfile-tests.cc b/libsefs/tests/fcfile-tests.cc
new file mode 100644
index 0000000..153d111
--- /dev/null
+++ b/libsefs/tests/fcfile-tests.cc
@@ -0,0 +1,333 @@
+/**
+ * @file
+ *
+ * Test the fcfile parsing and querying, introduced in SETools 3.3.
+ *
+ * @author Jeremy A. Mowery jmowery@tresys.com
+ * @author Jason Tang jtang@tresys.com
+ *
+ * Copyright (C) 2007 Tresys Technology, LLC
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#include <CUnit/CUnit.h>
+#include <sefs/fcfile.hh>
+#include <string.h>
+
+#define FC_CONFED SRCDIR "/file_contexts.confed"
+#define FC_UNION SRCDIR "/file_contexts.union"
+#define FC_BROKEN SRCDIR "/file_contexts.broken"
+
+static void fcfile_open()
+{
+ sefs_fcfile *fc1 = NULL, *fc2 = NULL;
+ bool open_failed = false;
+ apol_vector_t *files = apol_vector_create(NULL);
+ CU_ASSERT_PTR_NOT_NULL_FATAL(files);
+ apol_vector_append(files, (void *)(FC_CONFED));
+
+ try
+ {
+ fc1 = new sefs_fcfile(NULL, NULL);
+ fc2 = new sefs_fcfile(files, NULL, NULL);
+ }
+ catch(...)
+ {
+ CU_ASSERT(0);
+ open_failed = true;
+ }
+ delete fc1;
+ fc1 = NULL;
+ if (open_failed)
+ {
+ delete fc2;
+ return;
+ }
+
+ apol_vector_destroy(&files);
+ files = apol_vector_create(NULL);
+ CU_ASSERT_PTR_NOT_NULL_FATAL(files);
+ apol_vector_append(files, (void *)(FC_UNION));
+ apol_vector_append(files, (void *)(FC_BROKEN));
+ size_t num_matches = 0;
+ try
+ {
+ num_matches = fc2->appendFileList(files);
+ CU_ASSERT(num_matches == 1);
+ }
+ catch(...)
+ {
+ CU_ASSERT(0);
+ }
+ apol_vector_destroy(&files);
+
+ CU_ASSERT_FALSE(fc2->isMLS());
+ CU_ASSERT(fc2->fclist_type() == SEFS_FCLIST_TYPE_FCFILE);
+
+ const apol_vector_t *fileList = fc2->fileList();
+ CU_ASSERT(apol_vector_get_size(fileList) == 2);
+ if (apol_vector_get_size(fileList) >= 1)
+ {
+ CU_ASSERT_STRING_EQUAL(apol_vector_get_element(fileList, 0), FC_CONFED);
+ }
+ if (apol_vector_get_size(fileList) >= 2)
+ {
+ CU_ASSERT_STRING_EQUAL(apol_vector_get_element(fileList, 1), FC_UNION);
+ }
+
+ delete fc2;
+}
+
+int fcfile_query_map_user_lee(sefs_fclist * fc __attribute__ ((unused)), const sefs_entry * e, void *data)
+{
+ const apol_context_t *con = e->context();
+ if (strcmp(apol_context_get_user(con), "lee_u") == 0)
+ {
+ CU_ASSERT(1);
+ int *num_matches = static_cast < int *>(data);
+ (*num_matches)++;
+ CU_ASSERT_STRING_EQUAL(e->origin(), FC_CONFED);
+ return 0;
+ }
+ else
+ {
+ CU_ASSERT(0);
+ return -1;
+ }
+}
+
+static void fcfile_query()
+{
+ sefs_fcfile *fc = NULL;
+ int retval;
+ try
+ {
+ fc = new sefs_fcfile(FC_CONFED, NULL, NULL);
+ retval = fc->appendFile(FC_UNION);
+ CU_ASSERT(retval == 0);
+ }
+ catch(...)
+ {
+ CU_ASSERT_FATAL(0);
+ }
+
+ sefs_query *q = new sefs_query();
+ q->user("lee_u");
+ int num_matches = 0;
+ retval = fc->runQueryMap(q, fcfile_query_map_user_lee, &num_matches);
+ CU_ASSERT(retval == 0 && num_matches == 2);
+
+ q->user(NULL);
+ q->role("location_r");
+ apol_vector_t *entries = fc->runQuery(q);
+ CU_ASSERT_PTR_NOT_NULL(entries);
+ CU_ASSERT(apol_vector_get_size(entries) == 11);
+ for (size_t i = 0; i < apol_vector_get_size(entries); i++)
+ {
+ sefs_entry *e = static_cast < sefs_entry * >(apol_vector_get_element(entries, i));
+ const apol_context_t *con = e->context();
+ const char *t = apol_context_get_type(con);
+ CU_ASSERT(strcmp(t, "city_t") == 0 || strcmp(t, "state_t") == 0 || strcmp(t, "terrain_t") == 0);
+ char *s = e->toString();
+ printf("%s\n", s);
+ CU_ASSERT_PTR_NOT_NULL(s);
+ free(s);
+ }
+ apol_vector_destroy(&entries);
+
+ q->type("city_t", false);
+ entries = fc->runQuery(q); // both role and type are set
+ CU_ASSERT_PTR_NOT_NULL(entries);
+ CU_ASSERT(apol_vector_get_size(entries) == 3);
+ bool found_boonsboro = false, found_sharpsburg = false, found_harpers_ferry = false;
+ for (size_t i = 0; i < apol_vector_get_size(entries); i++)
+ {
+ sefs_entry *e = static_cast < sefs_entry * >(apol_vector_get_element(entries, i));
+ const char *p = e->path();
+ if (strcmp(p, "/antietam/boonsboro") == 0)
+ {
+ found_boonsboro = true;
+ }
+ else if (strcmp(p, "/sharpsburg") == 0)
+ {
+ found_sharpsburg = true;
+ }
+ else if (strcmp(p, "/harpers_ferry") == 0)
+ {
+ found_harpers_ferry = true;
+ }
+ else
+ {
+ CU_ASSERT(0);
+ }
+ }
+ CU_ASSERT(found_boonsboro && found_sharpsburg && found_harpers_ferry);
+ apol_vector_destroy(&entries);
+
+ q->role(NULL);
+ q->type(NULL, false);
+ q->objectClass(QPOL_CLASS_LNK_FILE);
+ entries = fc->runQuery(q);
+ CU_ASSERT_PTR_NOT_NULL(entries);
+ CU_ASSERT(apol_vector_get_size(entries) == 6); // 2 link files, 4 entries without explicit class
+ for (size_t i = 0; i < apol_vector_get_size(entries); i++)
+ {
+ sefs_entry *e = static_cast < sefs_entry * >(apol_vector_get_element(entries, i));
+ uint32_t objclass = e->objectClass();
+ CU_ASSERT(objclass == QPOL_CLASS_LNK_FILE || objclass == QPOL_CLASS_ALL);
+ }
+ apol_vector_destroy(&entries);
+
+ q->objectClass("file");
+ entries = fc->runQuery(q);
+ CU_ASSERT_PTR_NOT_NULL(entries);
+ CU_ASSERT(apol_vector_get_size(entries) == 8); // 4 files, 4 entries without explicit class
+ size_t num_files = 0, num_alls = 0;
+ for (size_t i = 0; i < apol_vector_get_size(entries); i++)
+ {
+ sefs_entry *e = static_cast < sefs_entry * >(apol_vector_get_element(entries, i));
+ uint32_t objclass = e->objectClass();
+ if (objclass == QPOL_CLASS_FILE)
+ {
+ num_files++;
+ }
+ else if (objclass == QPOL_CLASS_ALL)
+ {
+ num_alls++;
+ }
+ else
+ {
+ CU_ASSERT(0);
+ }
+ }
+ CU_ASSERT(num_files == 4 && num_alls == 4);
+ apol_vector_destroy(&entries);
+
+ // setting any of these should have no effect upon results
+ q->range("s0", APOL_QUERY_EXACT);
+ q->inode(42);
+ q->dev("first_maryland_campaign");
+ entries = fc->runQuery(q);
+ CU_ASSERT_PTR_NOT_NULL(entries);
+ CU_ASSERT(apol_vector_get_size(entries) == 8);
+ num_files = 0, num_alls = 0;
+ for (size_t i = 0; i < apol_vector_get_size(entries); i++)
+ {
+ sefs_entry *e = static_cast < sefs_entry * >(apol_vector_get_element(entries, i));
+ uint32_t objclass = e->objectClass();
+ if (objclass == QPOL_CLASS_FILE)
+ {
+ num_files++;
+ }
+ else if (objclass == QPOL_CLASS_ALL)
+ {
+ num_alls++;
+ }
+ else
+ {
+ CU_ASSERT(0);
+ }
+ }
+ CU_ASSERT(num_files == 4 && num_alls == 4);
+ apol_vector_destroy(&entries);
+
+ q->objectClass((const char *)NULL);
+ q->path("/sharpsburg/main_street");
+ entries = fc->runQuery(q);
+ CU_ASSERT_PTR_NOT_NULL(entries);
+ CU_ASSERT(apol_vector_get_size(entries) == 0);
+ apol_vector_destroy(&entries);
+
+ q->path("/sharpsburg/east_woods/hooker");
+ entries = fc->runQuery(q);
+ CU_ASSERT_PTR_NOT_NULL(entries);
+ CU_ASSERT(apol_vector_get_size(entries) == 2);
+ bool found_east_woods = false, found_hooker = false;
+ for (size_t i = 0; i < apol_vector_get_size(entries); i++)
+ {
+ sefs_entry *e = static_cast < sefs_entry * >(apol_vector_get_element(entries, i));
+ const char *path = e->path();
+ if (strcmp(path, "/sharpsburg/east_woods(/.*)?") == 0)
+ {
+ found_east_woods = true;
+ }
+ else if (strcmp(path, "/sharpsburg/east_woods/hooker") == 0)
+ {
+ found_hooker = true;
+ }
+ else
+ {
+ CU_ASSERT(0);
+ }
+ }
+ CU_ASSERT(found_east_woods && found_hooker);
+ apol_vector_destroy(&entries);
+
+ q->path(NULL);
+ q->user("hill");
+ q->regex(true);
+ entries = fc->runQuery(q);
+ CU_ASSERT_PTR_NOT_NULL(entries);
+ CU_ASSERT(apol_vector_get_size(entries) == 2);
+ apol_vector_destroy(&entries);
+
+ q->user("hilly");
+ entries = fc->runQuery(q);
+ CU_ASSERT_PTR_NOT_NULL(entries);
+ CU_ASSERT(apol_vector_get_size(entries) == 0);
+ apol_vector_destroy(&entries);
+
+ q->user(NULL);
+ q->path("/sharpsburg/dunker");
+ entries = fc->runQuery(q);
+ CU_ASSERT_PTR_NOT_NULL(entries);
+ CU_ASSERT(apol_vector_get_size(entries) == 1);
+ for (size_t i = 0; i < apol_vector_get_size(entries); i++)
+ {
+ sefs_entry *e = static_cast < sefs_entry * >(apol_vector_get_element(entries, i));
+ const apol_context_t *con = e->context();
+ CU_ASSERT_PTR_NOT_NULL(con);
+ CU_ASSERT_STRING_EQUAL(apol_context_get_user(con), "");
+ CU_ASSERT_STRING_EQUAL(apol_context_get_role(con), "");
+ CU_ASSERT_STRING_EQUAL(apol_context_get_type(con), "");
+ }
+ apol_vector_destroy(&entries);
+
+ delete q;
+ delete fc;
+}
+
+// alse test: load MLS in non-MLS; load non-MLS in MLS; MLS in MLS
+
+CU_TestInfo fcfile_tests[] = {
+ {"fcfile opening files", fcfile_open}
+ ,
+ {"fcfile queries", fcfile_query}
+ ,
+ CU_TEST_INFO_NULL
+};
+
+int fcfile_init()
+{
+ return 0;
+}
+
+int fcfile_cleanup()
+{
+ return 0;
+}
diff --git a/libsefs/tests/fcfile-tests.hh b/libsefs/tests/fcfile-tests.hh
new file mode 100644
index 0000000..e3aa8ed
--- /dev/null
+++ b/libsefs/tests/fcfile-tests.hh
@@ -0,0 +1,35 @@
+/**
+ * @file
+ *
+ * Declarations for libsefs file_contexts file (fcfile) tests.
+ *
+ * @author Jeremy A. Mowery jmowery@tresys.com
+ * @author Jason Tang jtang@tresys.com
+ *
+ * Copyright (C) 2007 Tresys Technology, LLC
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef FCFILE_TESTS_H
+#define FCFILE_TESTS_H
+
+#include <CUnit/CUnit.h>
+
+extern CU_TestInfo fcfile_tests[];
+extern int fcfile_init();
+extern int fcfile_cleanup();
+
+#endif
diff --git a/libsefs/tests/file_contexts.broken b/libsefs/tests/file_contexts.broken
new file mode 100644
index 0000000..b74dd72
--- /dev/null
+++ b/libsefs/tests/file_contexts.broken
@@ -0,0 +1,3 @@
+# An intentionally broken file_contexts file.
+
+/antietam/burnside -? burnside_u:union_r:general_t
diff --git a/libsefs/tests/file_contexts.confed b/libsefs/tests/file_contexts.confed
new file mode 100644
index 0000000..01d48a1
--- /dev/null
+++ b/libsefs/tests/file_contexts.confed
@@ -0,0 +1,24 @@
+# Confederate placement as of morning of September 17, 1861.
+
+/ -d maryland_u:location_r:state_t
+/sharpsburg -d sharpsburg_u:location_r:city_t
+/sharpsburg/lee -l lee_u:confed_r:general_t
+/sharpsburg/nicodemus -d sharpsburg_u:location_r:terrain_t
+/sharpsburg/nicodemus/jackson(/.*)? -- jackson_u:confed_r:infantry_t
+/sharpsburg/nicodemus/stuart -- stuart_u:confed_r:artillery_t
+/sharpsburg/west_woods -d sharpsburg_u:location_r:terrain_t
+/sharpsburg/west_woods/jackson(/.*)? -c jackson_u:confed_r:infantry_t
+/sharpsburg/west_woods/jones -c jones_u:confed_r:infantry_t
+/sharpsburg/west_woods/lee -c lee_u:confed_r:artillery_t
+/sharpsburg/dunker <<none>>
+/sharpsburg/hill -p hill_u:confed_r:infantry_t
+/antietam -d sharpsburg_u:location_r:terrain_t
+/antietam/cemetary_hill -d sharpsburg_u:location_r:terrain_t
+/antietam/cemetary_hill/artillery_ridge -d sharpsburg_u:location_r:terrain_t
+/antietam/cemetary_hill/artillery_ridge/anderson(/.*)? -s anderson_u:confed_r:infantry_t
+/antietam/cemetary_hill/artillery_ridge/jones(/.*)? -s jones_u:confed_r:infantry_t
+/antietam/cemetary_hill/artillery_ridge/walker(/.*)? -s walker_u:confed_r:infantry_t
+/antietam/cemetary_hill/artillery_ridge/toombs(/.*)? -s toombs_u:confed_r:artillery_t
+
+/harpers_ferry virginia_u:location_r:city_t
+/harpers_ferry/.* hill_u:confed_r:infantry_t
diff --git a/libsefs/tests/file_contexts.union b/libsefs/tests/file_contexts.union
new file mode 100644
index 0000000..4e93e9f
--- /dev/null
+++ b/libsefs/tests/file_contexts.union
@@ -0,0 +1,16 @@
+# Union forces as of morning of September 17, 1861.
+
+/antietam/mcclellan -l mcclellan_u:union_r:general_t
+/antietam/boonsboro -- maryland_u:location_r:city_t
+/sharpsburg/north_woods -d sharpsburg_u:location_r:terrain_t
+/sharpsburg/north_woods/doubleday -b doubleday_u:union_r:infantry_t
+/sharpsburg/north_woods/meade.* -b meade_u:union_r:artillery_t
+/sharpsburg/north_woods/ricketts -b ricketts_u:union_r:cavalry_t
+/sharpsburg/east_woods(/.*)? sharpsburg_u:location_r:terrain_t
+/sharpsburg/east_woods/hooker -p hooker_u:union_r:infantry_t
+/sharpsburg/east_woods/mansfield -p mansfield_u:union_r:artillery_t
+/sharpsburg/east_woods/sedgwick -p sedgwick_u:union_r:infantry_t
+/sharpsburg/corn_field -- <<none>>
+/antietam/middle_bridge -b <<none>>
+/antietam/lower_bridge -b <<none>>
+/antietam/snavely -p <<none>>
diff --git a/libsefs/tests/libsefs-tests.cc b/libsefs/tests/libsefs-tests.cc
new file mode 100644
index 0000000..9104d41
--- /dev/null
+++ b/libsefs/tests/libsefs-tests.cc
@@ -0,0 +1,52 @@
+/**
+ * @file
+ *
+ * CUnit testing framework for libsefs.
+ *
+ * @author Jeremy A. Mowery jmowery@tresys.com
+ * @author Jason Tang jtang@tresys.com
+ *
+ * Copyright (C) 2007 Tresys Technology, LLC
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#include <CUnit/CUnit.h>
+#include <CUnit/Basic.h>
+
+#include "fcfile-tests.hh"
+
+int main(void)
+{
+ if (CU_initialize_registry() != CUE_SUCCESS)
+ {
+ return CU_get_error();
+ }
+
+ CU_SuiteInfo suites[] = {
+ {"fcfile", fcfile_init, fcfile_cleanup, fcfile_tests}
+ ,
+ CU_SUITE_INFO_NULL
+ };
+
+ CU_register_suites(suites);
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ unsigned int num_failures = CU_get_number_of_failure_records();
+ CU_cleanup_registry();
+ return (int)num_failures;
+}