Patchwork [6/7] libjunction: Store junctions in XML format

login
register
mail settings
Submitter Chuck Lever
Date Jan. 4, 2012, 9:07 p.m.
Message ID <20120104210706.8810.125.stgit@degas.1015granger.net>
Download mbox | patch
Permalink /patch/134362/
State Accepted
Headers show

Comments

Chuck Lever - Jan. 4, 2012, 9:07 p.m.
Until now, junctions have had to contain just a few data items.  Easy.

But soon we will want support for "NFS junctions," which will store
NFS fs_locations4 and fs_locations_info4 data right in the local file
system.  NFS locations data is a complex data type, with arrays of
strings, integers, and booleans, in records of variable size.  We need
something richer to store junction data on disk.  Enter XML.

The XML for a FedFS junction looks something like this:

  <?xml version="1.0" encoding="UTF-8"?>
  <junction>
    <fileset>
      <name fsnuuid="d2b09895-98ff-415e-ac73-565fad7b429b"
            nsdbname="nsdb.example.net"
            nsdbport="389" />
    </fileset>
  </junction>

It can be read and edited by any text editor that can access an
extended attribute.  Junction XML can easily be extended at any time
by adding new child elements of <junction> and <fileset>.

Currently there is a separate extended attribute that stores the
junction type, and FedFS junction data is then spread across several
additional extended attributes.  Thanks to XML, complex junction data
can be flattened and stored in a single extended attribute.  That way
junction data for multiple file system types can be stored in one
object, each junction type in its own extended attribute.

Our junctions will contain _either_ a FedFS fileset name, _or_ a
set of fileset locations.  We can store both in the same attribute,
as it makes no sense to have both types of junction data present
in the same junction.  Let's use an attribute called
trusted.junction.nfs .

This commit makes an incompatible change to on-disk junctions.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---

 configure.ac                        |    5 
 src/fedfsc/Makefile.am              |    2 
 src/fedfsd/Makefile.am              |    2 
 src/libjunction/Makefile.am         |    5 
 src/libjunction/fedfs.c             |  529 ++++++++++++++++++++++++++---------
 src/libjunction/junction-internal.h |   61 ++++
 src/libjunction/junction.c          |   59 ++++
 src/libjunction/xml.c               |  244 ++++++++++++++++
 src/nsdbc/Makefile.am               |    2 
 src/nsdbparams/Makefile.am          |    2 
 src/resolve-junction/Makefile.am    |    2 
 11 files changed, 758 insertions(+), 155 deletions(-)
 create mode 100644 src/libjunction/xml.c

Patch

diff --git a/configure.ac b/configure.ac
index 5d1eca0..a5afb12 100644
--- a/configure.ac
+++ b/configure.ac
@@ -118,6 +118,11 @@  AC_CHECK_LIB([resolv], [__res_querydomain],
 		 AC_DEFINE([HAVE_LIBRESOLV], [1],
 			   [Define if you have libresolv])],
 		[AC_MSG_ERROR([libresolv not found.])])
+AC_CHECK_LIB([xml2], [xmlParseFile],
+		[AC_SUBST([LIBXML2], ["-lxml2"])
+		 AC_DEFINE([HAVE_LIBXML2], [1],
+			   [Define if you have libxml2])],
+		[AC_MSG_ERROR([libxml2 not found.])])
 
 # Checks for header files.
 AC_CHECK_HEADERS([fcntl.h langinfo.h locale.h memory.h netdb.h netinet/in.h stdint.h stdlib.h string.h sys/socket.h syslog.h termios.h unistd.h wchar.h])
diff --git a/src/fedfsc/Makefile.am b/src/fedfsc/Makefile.am
index 2659e35..87189e2 100644
--- a/src/fedfsc/Makefile.am
+++ b/src/fedfsc/Makefile.am
@@ -30,7 +30,7 @@  sbin_PROGRAMS		= fedfs-null \
 			  fedfs-lookup-junction \
 			  fedfs-create-replication fedfs-delete-replication \
 			  fedfs-lookup-replication
-LDADD			= $(LIBTIRPC) $(LIBLDAP) $(LIBLBER) \
+LDADD			= $(LIBTIRPC) $(LIBLDAP) $(LIBLBER) $(LIBXML2) \
 			  $(LIBSQLITE3) $(LIBIDN) $(LIBUUID) \
 			  $(top_builddir)/src/libadmin/libadmin.la \
 			  $(top_builddir)/src/libjunction/libjunction.la \
diff --git a/src/fedfsd/Makefile.am b/src/fedfsd/Makefile.am
index 37fd42f..47ee106 100644
--- a/src/fedfsd/Makefile.am
+++ b/src/fedfsd/Makefile.am
@@ -27,7 +27,7 @@  noinst_HEADERS		= fedfsd.h
 RPCPREFIX		= rpc.
 sbin_PROGRAMS		= fedfsd
 fedfsd_SOURCES		= listen.c main.c privilege.c svc.c
-fedfsd_LDADD		= $(LIBTIRPC) $(LIBLDAP) $(LIBLBER) \
+fedfsd_LDADD		= $(LIBTIRPC) $(LIBLDAP) $(LIBLBER) $(LIBXML2) \
 			  $(LIBSQLITE3) $(LIBIDN) $(LIBUUID) $(LIBCAP) \
 			  $(top_builddir)/src/libadmin/libadmin.la \
 			  $(top_builddir)/src/libnsdb/libnsdb.la \
diff --git a/src/libjunction/Makefile.am b/src/libjunction/Makefile.am
index e2a0a4f..9d91fef 100644
--- a/src/libjunction/Makefile.am
+++ b/src/libjunction/Makefile.am
@@ -26,7 +26,7 @@ 
 noinst_HEADERS		= junction-internal.h
 
 noinst_LTLIBRARIES	= libjunction.la
-libjunction_la_SOURCES	= export-cache.c fedfs.c junction.c
+libjunction_la_SOURCES	= export-cache.c fedfs.c junction.c xml.c
 
 CLEANFILES		= cscope.in.out cscope.out cscope.po.out *~
 DISTCLEANFILES		= Makefile.in
@@ -34,4 +34,5 @@  DISTCLEANFILES		= Makefile.in
 AM_CFLAGS		= -ggdb -fstrict-aliasing -fPIC \
 			  -Wall -Wextra -pedantic -Wformat=2 \
 			  -Wstrict-aliasing=2 -Wp,-D_FORTIFY_SOURCE=2
-AM_CPPFLAGS		= -I. -I$(top_srcdir)/src/include
+AM_CPPFLAGS		= -I. -I$(top_srcdir)/src/include \
+			  -I/usr/include/libxml2
diff --git a/src/libjunction/fedfs.c b/src/libjunction/fedfs.c
index d7af6cc..bbd0a65 100644
--- a/src/libjunction/fedfs.c
+++ b/src/libjunction/fedfs.c
@@ -23,6 +23,32 @@ 
  *	http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
  */
 
+/*
+ * A FedFS junction is an FSN, represented in a well-formed XML document:
+ *
+ * <?xml version="1.0" encoding="UTF-8"?>
+ * <junction>
+ *   <fileset>
+ *     <name fsnuuid="d2b09895-98ff-415e-ac73-565fad7b429b"
+ *           nsdbname="nsdb.example.net"
+ *           nsdbport="389" />
+ *   </fileset>
+ * </junction>
+ *
+ * FedFS junction XML is stored in an extended attribute called
+ * "trusted.junction.nfs".   The parent object is a directory.
+ *
+ * To help file servers discover junctions efficiently, the directory
+ * has no execute bits, and the sticky bit is set.  In addition, an
+ * extended attribute called "trusted.junction.type" is added.  The
+ * contents are ignored in user space.
+ *
+ * Finally, for pre-existing directories that are converted to
+ * junctions, their mode bits are saved in an extended attribute called
+ * "trusted.junction.mode".  When the junction data is removed, the
+ * directory's mode bits are restored from this information.
+ */
+
 #include <sys/types.h>
 #include <sys/stat.h>
 
@@ -31,14 +57,6 @@ 
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
-#include <fcntl.h>
-#include <wchar.h>
-#include <memory.h>
-#include <signal.h>
-#include <errno.h>
-#include <dirent.h>
-
-#include <attr/xattr.h>
 
 #include "fedfs.h"
 #include "nsdb.h"
@@ -47,29 +65,30 @@ 
 #include "xlog.h"
 
 /**
- * Magic string planted in the "type" attribute of junctions
+ * Tag name of FSN element of a junction XML document
  */
-#define FEDFS_JUNCTION_TYPE		"fedfs"
+#define FEDFS_XML_FSN_TAG		(const xmlChar *)"name"
 
 /**
- * Name of extended attribute containing junction type
+ * Name of FSN UUID attribute on a fileset name element
  */
-#define FEDFS_XATTR_NAME_TYPE		"trusted.junction.type"
+#define FEDFS_XML_FSN_UUID_ATTR		(const xmlChar *)"fsnuuid"
 
 /**
- * Name of extended attribute containing junction's FSN UUID
+ * Name of NSDB hostname attribute on a fileset name element
  */
-#define FEDFS_XATTR_NAME_FSNUUID	"trusted.junction.fsnuuid"
+#define FEDFS_XML_FSN_NSDBNAME_ATTR	(const xmlChar *)"nsdbname"
 
 /**
- * Name of extended attribute containing hostname of NSDB service
+ * Name of NSDB port attribute on a fileset name element
  */
-#define FEDFS_XATTR_NAME_NSDB		"trusted.junction.nsdbname"
+#define FEDFS_XML_FSN_NSDBPORT_ATTR	(const xmlChar *)"nsdbport"
 
 /**
- * Name of extended attribute containing port of NSDB service
+ * XPath path to first fileset name element in a junction document
  */
-#define FEDFS_XATTR_NAME_PORT		"trusted.junction.nsdbport"
+#define FEDFS_XML_FSN_XPATH		(const xmlChar *)	\
+						"/junction/fileset/name[1]"
 
 
 /**
@@ -90,63 +109,154 @@  fedfs_remove_fsn(const char *pathname)
 	if (retval != FEDFS_OK)
 		return retval;
 
-	retval = junction_remove_xattr(fd, pathname, FEDFS_XATTR_NAME_TYPE);
-	if (retval != FEDFS_OK)
-		goto out;
-	retval = junction_remove_xattr(fd, pathname, FEDFS_XATTR_NAME_FSNUUID);
-	if (retval != FEDFS_OK)
-		goto out;
-	retval = junction_remove_xattr(fd, pathname, FEDFS_XATTR_NAME_NSDB);
-	if (retval != FEDFS_OK)
-		goto out;
-	retval = junction_remove_xattr(fd, pathname, FEDFS_XATTR_NAME_PORT);
+	retval = junction_remove_xattr(fd, pathname, JUNCTION_XATTR_NAME_NFS);
 
-out:
 	(void)close(fd);
 	return retval;
 }
 
 /**
- * Store FedFS information into a junction
+ * Construct fileset's "name" element
  *
  * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param fileset parent node of new FSN element
  * @param fsn_uuid NUL-terminated C string containing FSN UUID to store
- * @param host an initialized nsdb_t object
+ * @param nsdb_name a NUL-terminated C string containing NSDB hostname
+ * @param nsdb_port the port number of the NSDB
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+fedfs_name_xml(const char *pathname, xmlNodePtr fileset,
+		const char *fsn_uuid, const char *nsdb_name,
+		unsigned short nsdb_port)
+{
+	xmlNodePtr new;
+
+	new = xmlNewTextChild(fileset , NULL, FEDFS_XML_FSN_TAG, NULL);
+	if (new == NULL) {
+		xlog(D_GENERAL, "%s: Failed to add FSN element for %s\n",
+			__func__, pathname);
+		return FEDFS_ERR_SVRFAULT;
+	}
+
+	xmlSetProp(new, FEDFS_XML_FSN_UUID_ATTR, (const xmlChar *)fsn_uuid);
+	xmlSetProp(new, FEDFS_XML_FSN_NSDBNAME_ATTR, (const xmlChar *)nsdb_name);
+	if (nsdb_port != LDAP_PORT)
+		junction_xml_set_int_attribute(new,
+						FEDFS_XML_FSN_NSDBPORT_ATTR,
+						nsdb_port);
+
+	return FEDFS_OK;
+}
+
+/**
+ * Construct a "fileset" element
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param root root element of XML document tree
+ * @param fsn_uuid NUL-terminated C string containing FSN UUID to store
+ * @param nsdb_name a NUL-terminated C string containing NSDB hostname
+ * @param nsdb_port the port number of the NSDB
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+fedfs_fileset_xml(const char *pathname, xmlNodePtr root,
+		const char *fsn_uuid, const char *nsdb_name,
+		unsigned short nsdb_port)
+{
+	xmlNodePtr fileset;
+
+	fileset = xmlNewTextChild(root, NULL, JUNCTION_XML_FILESET_TAG, NULL);
+	if (fileset == NULL) {
+		xlog(D_GENERAL, "%s: Failed to add fileset element for %s\n",
+			__func__, pathname);
+		return FEDFS_ERR_SVRFAULT;
+	}
+
+	return fedfs_name_xml(pathname, fileset, fsn_uuid, nsdb_name, nsdb_port);
+}
+
+/**
+ * Construct FedFS junction XML document
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param doc an XML parse tree in which to construct the junction XML document
+ * @param fsn_uuid NUL-terminated C string containing FSN UUID to store
+ * @param nsdb_name a NUL-terminated C string containing NSDB hostname
+ * @param nsdb_port the port number of the NSDB
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+fedfs_build_xml(const char *pathname, xmlDocPtr doc,
+		const char *fsn_uuid, const char *nsdb_name,
+		unsigned short nsdb_port)
+{
+	xmlNodePtr root;
+
+	root = xmlNewNode(NULL, JUNCTION_XML_ROOT_TAG);
+	if (root == NULL) {
+		xlog(D_GENERAL, "%s: Failed to create root element for %s\n",
+			__func__, pathname);
+		return FEDFS_ERR_SVRFAULT;
+	}
+	(void)xmlDocSetRootElement(doc, root);
+
+	return fedfs_fileset_xml(pathname, root, fsn_uuid, nsdb_name, nsdb_port);
+}
+
+/**
+ * Write FedFS information into a FedFS junction extended attribute
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param doc an empty XML parse tree in which to construct the junction XML document
+ * @param fsn_uuid NUL-terminated C string containing FSN UUID to store
+ * @param nsdb_name a NUL-terminated C string containing NSDB hostname
+ * @param nsdb_port the port number of the NSDB
  * @return a FedFsStatus code
  *
  * @note Access to trusted attributes requires CAP_SYS_ADMIN.
  */
 static FedFsStatus
-fedfs_store_fsn(const char *pathname, const char *fsn_uuid, const nsdb_t host)
+fedfs_write_junction(const char *pathname, xmlDocPtr doc,
+		const char *fsn_uuid, const char *nsdb_name,
+		unsigned short nsdb_port)
 {
 	FedFsStatus retval;
-	char buf[20];
-	int fd, len;
 
-	retval = junction_open_path(pathname, &fd);
+	retval = fedfs_build_xml(pathname, doc, fsn_uuid, nsdb_name, nsdb_port);
 	if (retval != FEDFS_OK)
 		return retval;
 
-	retval = junction_set_xattr(fd, pathname, FEDFS_XATTR_NAME_TYPE,
-			FEDFS_XATTR_NAME_TYPE, sizeof(FEDFS_XATTR_NAME_TYPE));
-	if (retval != FEDFS_OK)
-		goto out;
+	return junction_xml_write(pathname, JUNCTION_XATTR_NAME_NFS, doc);
+}
 
-	retval = junction_set_xattr(fd, pathname, FEDFS_XATTR_NAME_FSNUUID,
-			fsn_uuid, strlen(fsn_uuid) + 1);
-	if (retval != FEDFS_OK)
-		goto out;
+/**
+ * Store FedFS information into a junction object
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param fsn_uuid NUL-terminated C string containing FSN UUID to store
+ * @param host an initialized nsdb_t object
+ * @return a FedFsStatus code
+ *
+ * @note Access to trusted attributes requires CAP_SYS_ADMIN.
+ */
+static FedFsStatus
+fedfs_store_fsn(const char *pathname, const char *fsn_uuid, const nsdb_t host)
+{
+	FedFsStatus retval;
+	xmlDocPtr doc;
 
-	retval = junction_set_xattr(fd, pathname, FEDFS_XATTR_NAME_NSDB,
-			nsdb_hostname(host), nsdb_hostname_len(host) + 1);
-	if (retval != FEDFS_OK)
-		goto out;
+	doc = xmlNewDoc((xmlChar *)"1.0");
+	if (doc == NULL) {
+		xlog(D_GENERAL, "%s: Failed to create XML doc for %s\n",
+			__func__, pathname);
+		return FEDFS_ERR_SVRFAULT;
+	}
 
-	len = snprintf(buf, sizeof(buf), "%u", nsdb_port(host));
-	retval = junction_set_xattr(fd, pathname, FEDFS_XATTR_NAME_PORT, buf, len + 1);
+	retval = fedfs_write_junction(pathname, doc, fsn_uuid,
+					nsdb_hostname(host), nsdb_port(host));
 
-out:
-	(void)close(fd);
+	xmlFreeDoc(doc);
 	return retval;
 }
 
@@ -181,6 +291,11 @@  fedfs_add_junction(const char *pathname, const char *fsn_uuid, const nsdb_t host
 	if (retval != FEDFS_OK)
 		goto out_err;
 
+	/* The content of this attribute is ignored */
+	retval = junction_add_type(pathname, "nfs");
+	if (retval != FEDFS_OK)
+		return retval;
+
 	return retval;
 
 out_err:
@@ -206,6 +321,10 @@  fedfs_delete_junction(const char *pathname)
 	if (retval != FEDFS_OK)
 		return retval;
 
+	retval = junction_remove_type(pathname);
+	if (retval != FEDFS_OK)
+		return retval;
+
 	retval = junction_restore_mode(pathname);
 	if (retval != FEDFS_OK)
 		return retval;
@@ -214,72 +333,198 @@  fedfs_delete_junction(const char *pathname)
 }
 
 /**
- * Retrieve FSN information from a FedFS junction
+ * Parse fileset name information from an XML node
  *
  * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param node root of XML parse subtree containing fileset name element
  * @param fsn_uuid OUT: NUL-terminated C string containing FSN UUID to store
  * @param host OUT: an initialized nsdb_t object
  * @return a FedFsStatus code
  *
- * Caller must free the string returned in "fsn_uuid" with free(3), and
- * free the NSDB host returned in "host" with nsdb_free_nsdb().
+ * If fedfs_parse_node() returns FEDFS_OK, caller must free the string
+ * returned in "fsn_uuid" with free(3) and the NSDB host returned in "host"
+ * with nsdb_free_nsdb().
  */
-FedFsStatus
-fedfs_get_fsn(const char *pathname, char **fsn_uuid, nsdb_t *host)
+static FedFsStatus
+fedfs_parse_node(const char *pathname, xmlNodePtr node,
+		char **fsn_uuid, nsdb_t *host)
 {
-	void *uuid_tmp = NULL;
-	void *nsdbname_tmp = NULL;
-	void *port_tmp = NULL;
-	nsdb_t host_tmp = NULL;
-	unsigned short port;
+	xmlChar *nsdb_name_tmp, *fsn_uuid_tmp;
 	FedFsStatus retval;
-	size_t len;
-	int fd;
-
-	if (fsn_uuid == NULL || host == NULL)
-		return FEDFS_ERR_INVAL;
+	nsdb_t host_tmp;
+	char *tmp;
+	int port;
 
-	retval = junction_open_path(pathname, &fd);
-	if (retval != FEDFS_OK)
-		return retval;
+	fsn_uuid_tmp = xmlGetProp(node, FEDFS_XML_FSN_UUID_ATTR);
+	nsdb_name_tmp = xmlGetProp(node, FEDFS_XML_FSN_NSDBNAME_ATTR);
 
-	retval = junction_get_xattr(fd, pathname, FEDFS_XATTR_NAME_FSNUUID,
-							&uuid_tmp, &len);
-	if (retval != FEDFS_OK)
-		goto out_err;
+	retval = FEDFS_ERR_NOTJUNCT;
+	if (fsn_uuid_tmp == NULL) {
+		xlog(D_GENERAL, "%s: No UUID found in %s\n",
+			__func__, pathname);
+		goto out;
+	}
+	if (nsdb_name_tmp == NULL) {
+		xlog(D_GENERAL, "%s: No NSDB name found in %s\n",
+			__func__, pathname);
+		goto out;
+	}
 
-	retval = junction_get_xattr(fd, pathname, FEDFS_XATTR_NAME_NSDB,
-					&nsdbname_tmp, &len);
-	if (retval != FEDFS_OK)
-		goto out_err;
-	retval = junction_get_xattr(fd, pathname, FEDFS_XATTR_NAME_PORT,
-					&port_tmp, &len);
-	if (retval != FEDFS_OK)
-		goto out_err;
+	if (!junction_xml_get_int_attribute(node,
+					FEDFS_XML_FSN_NSDBPORT_ATTR, &port))
+		port = LDAP_PORT;
+	if (port < 1 || port > UINT16_MAX) {
+		xlog(D_GENERAL, "%s: Bad NSDB port value in %s\n",
+			__func__, pathname);
+		goto out;
+	}
 
 	retval = FEDFS_ERR_SVRFAULT;
-	if (!nsdb_parse_port_string(port_tmp, &port))
-		goto out_err;
+	tmp = strdup((char *)fsn_uuid_tmp);
+	if (tmp == NULL)
+		goto out;
 
 	retval = FEDFS_ERR_NSDB_PARAMS;
-	if (nsdb_lookup_nsdb(nsdbname_tmp, port, &host_tmp, NULL) != FEDFS_OK)
-		goto out_err;
+	if (nsdb_lookup_nsdb((const char *)nsdb_name_tmp, (unsigned short)port,
+					&host_tmp, NULL) != FEDFS_OK) {
+		xlog(D_GENERAL, "%s: No NSDB params for %s\n",
+			__func__, nsdb_name_tmp);
+		free(tmp);
+		goto out;
+	}
 
-	*fsn_uuid = uuid_tmp;
+	*fsn_uuid = tmp;
 	*host = host_tmp;
 	retval = FEDFS_OK;
 
 out:
-	free(port_tmp);
-	free(nsdbname_tmp);
-	(void)close(fd);
+	xmlFree(nsdb_name_tmp);
+	xmlFree(fsn_uuid_tmp);
 	return retval;
+}
 
-out_err:
-	nsdb_free_nsdb(host_tmp);
-	free(uuid_tmp);
-	goto out;
-	
+/**
+ * Parse fileset name information from a nodeset
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param nodeset XML path context containing junction XML
+ * @param fsn_uuid OUT: NUL-terminated C string containing FSN UUID to store
+ * @param host OUT: an initialized nsdb_t object
+ * @return a FedFsStatus code
+ *
+ * If fedfs_parse_nodeset() returns FEDFS_OK, caller must free the string
+ * returned in "fsn_uuid" with free(3) and the NSDB host returned in "host"
+ * with nsdb_free_nsdb().
+ */
+static FedFsStatus
+fedfs_parse_nodeset(const char *pathname, xmlNodeSetPtr nodeset,
+		char **fsn_uuid, nsdb_t *host)
+{
+	if (xmlXPathNodeSetIsEmpty(nodeset)) {
+		xlog(D_GENERAL, "%s: No fileset names found in %s\n",
+			__func__, pathname);
+		return FEDFS_ERR_NOTJUNCT;
+	}
+
+	return fedfs_parse_node(pathname, nodeset->nodeTab[0],
+					fsn_uuid, host);
+}
+
+/**
+ * Parse fileset name information from junction XML
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param context XML path context containing junction XML
+ * @param fsn_uuid OUT: NUL-terminated C string containing FSN UUID to store
+ * @param host OUT: an initialized nsdb_t object
+ * @return a FedFsStatus code
+ *
+ * If fedfs_parse_context() returns FEDFS_OK, caller must free the string
+ * returned in "fsn_uuid" with free(3) and the NSDB host returned in "host"
+ * with nsdb_free_nsdb().
+ */
+static FedFsStatus
+fedfs_parse_context(const char *pathname, xmlXPathContextPtr context,
+		char **fsn_uuid, nsdb_t *host)
+{
+	xmlXPathObjectPtr object;
+	FedFsStatus retval;
+
+	object = xmlXPathEvalExpression(FEDFS_XML_FSN_XPATH, context);
+	if (object == NULL) {
+		xlog(D_GENERAL, "%s: Failed to evaluate XML in %s\n",
+			__func__, pathname);
+		return FEDFS_ERR_NOTJUNCT;
+	}
+
+	retval = fedfs_parse_nodeset(pathname, object->nodesetval,
+					fsn_uuid, host);
+
+	xmlXPathFreeObject(object);
+	return retval;
+}
+
+/**
+ * Parse fileset name information from junction XML
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param doc XML parse tree containing junction XML document
+ * @param fsn_uuid OUT: NUL-terminated C string containing FSN UUID to store
+ * @param host OUT: an initialized nsdb_t object
+ * @return a FedFsStatus code
+ *
+ * If fedfs_parse_xml() returns FEDFS_OK, caller must free the string returned
+ * in "fsn_uuid" with free(3) and the NSDB host returned in "host" with
+ * nsdb_free_nsdb().
+ */
+static FedFsStatus
+fedfs_parse_xml(const char *pathname, xmlDocPtr doc, char **fsn_uuid, nsdb_t *host)
+{
+	xmlXPathContextPtr context;
+	FedFsStatus retval;
+
+	context = xmlXPathNewContext(doc);
+	if (context == NULL) {
+		xlog(D_GENERAL, "%s: Failed to create XPath context from %s\n",
+			__func__, pathname);
+		return FEDFS_ERR_SVRFAULT;
+	}
+
+	retval = fedfs_parse_context(pathname, context, fsn_uuid, host);
+
+	xmlXPathFreeContext(context);
+	return retval;
+}
+
+/**
+ * Retrieve FSN information from a FedFS junction
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param fsn_uuid OUT: NUL-terminated C string containing FSN UUID to store
+ * @param host OUT: an initialized nsdb_t object
+ * @return a FedFsStatus code
+ *
+ * If fedfs_get_fsn() returns FEDFS_OK, caller must free the string returned
+ * in "fsn_uuid" with free(3) and the NSDB host returned in "host" with
+ * nsdb_free_nsdb().
+ */
+FedFsStatus
+fedfs_get_fsn(const char *pathname, char **fsn_uuid, nsdb_t *host)
+{
+	FedFsStatus retval;
+	xmlDocPtr doc;
+
+	if (fsn_uuid == NULL || host == NULL)
+		return FEDFS_ERR_INVAL;
+
+	retval = junction_xml_parse(pathname, JUNCTION_XATTR_NAME_NFS, &doc);
+	if (retval != FEDFS_OK)
+		return retval;
+
+	retval = fedfs_parse_xml(pathname, doc, fsn_uuid, host);
+
+	xmlFreeDoc(doc);
+	return retval;
 }
 
 /**
@@ -292,7 +537,7 @@  out_err:
  *	FEDFS_ERR_NOTJUNCT:	"pathname" refers to an object that can be
  *				made into a FedFS junction
  *	FEDFS_ERR_EXIST:	"pathname" refers to something that is
- *				already a FedFS junction
+ *				already a junction
  *	FEDFS_ERR_INVAL:	"pathname" does not exist
  *	Other:			Some error occurred, "pathname" not
  *				investigated
@@ -321,37 +566,7 @@  fedfs_is_prejunction(const char *pathname)
 		goto out_close;
 	}
 
-	retval = junction_is_xattr_present(fd, pathname, FEDFS_XATTR_NAME_TYPE);
-	switch (retval) {
-	case FEDFS_ERR_NOTJUNCT:
-		break;
-	case FEDFS_OK:
-		goto out_exist;
-	default:
-		goto out_close;
-	}
-
-	retval = junction_is_xattr_present(fd, pathname, FEDFS_XATTR_NAME_FSNUUID);
-	switch (retval) {
-	case FEDFS_ERR_NOTJUNCT:
-		break;
-	case FEDFS_OK:
-		goto out_exist;
-	default:
-		goto out_close;
-	}
-
-	retval = junction_is_xattr_present(fd, pathname, FEDFS_XATTR_NAME_NSDB);
-	switch (retval) {
-	case FEDFS_ERR_NOTJUNCT:
-		break;
-	case FEDFS_OK:
-		goto out_exist;
-	default:
-		goto out_close;
-	}
-	
-	retval = junction_is_xattr_present(fd, pathname, FEDFS_XATTR_NAME_PORT);
+	retval = junction_is_xattr_present(fd, pathname, JUNCTION_XATTR_NAME_NFS);
 	switch (retval) {
 	case FEDFS_ERR_NOTJUNCT:
 		break;
@@ -370,20 +585,56 @@  out_exist:
 }
 
 /**
- * Predicate: does "pathname" refer to a FedFS junction?
+ * Verify that junction contains FedFS junction XML
  *
  * @param pathname NUL-terminated C string containing pathname of a directory
  * @return a FedFsStatus code
  *
- * Returns FEDFS_OK if "pathname" refers to a junction, or
- * FEDFS_ERR_NOTJUNCT if "pathname" does not refer to a junction, or
- * FEDFS_ERR_INVAL if "pathname" refers to something that does not exist.
- * Other errors may trickle up from lower layers.
+ * Return values:
+ *	FEDFS_OK:		"pathname" refers to a FedFS junction
+ *	FEDFS_ERR_NOTJUNCT:	"pathname" refers to something that is
+ *				not a FedFS junction
+ *	FEDFS_ERR_INVAL:	"pathname" does not exist
+ *	Other:			Some error occurred, "pathname" not
+ *				investigated
+ *
+ * NB: This is an expensive test.  However, it is only done if the object
+ * actually has a junction extended attribute, meaning it should be done
+ * rarely.  If this is really a problem, we can make the XML test cheaper.
+ */
+static FedFsStatus
+fedfs_is_junction_xml(const char *pathname)
+{
+	FedFsStatus retval;
+	char *fsn_uuid;
+	xmlDocPtr doc;
+	nsdb_t host;
+
+	retval = junction_xml_parse(pathname, JUNCTION_XATTR_NAME_NFS, &doc);
+	if (retval != FEDFS_OK)
+		return retval;
+
+	retval = fedfs_parse_xml(pathname, doc, &fsn_uuid, &host);
+	if (retval != FEDFS_OK)
+		goto out;
+
+	free(fsn_uuid);
+	nsdb_free_nsdb(host);
+out:
+	xmlFreeDoc(doc);
+	return retval;
+}
+
+/**
+ * Predicate: does "pathname" refer to a FedFS junction?
+ *
+ * @param pathname NUL-terminated C string containing pathname of a directory
+ * @return a FedFsStatus code
  *
  * Return values:
  *	FEDFS_OK:		"pathname" refers to a FedFS junction
- *	FEDFS_ERR_NOTJUNCT:	"pathname" refers to an object that can be
- *				made into a FedFS junction
+ *	FEDFS_ERR_NOTJUNCT:	"pathname" refers to an object that is
+ *				not a FedFS junction
  *	FEDFS_ERR_INVAL:	"pathname" does not exist
  *	Other:			Some error occurred, "pathname" not
  *				investigated
@@ -406,19 +657,13 @@  fedfs_is_junction(const char *pathname)
 	if (retval != FEDFS_OK)
 		goto out_close;
 
-	retval = junction_is_xattr_present(fd, pathname, FEDFS_XATTR_NAME_TYPE);
+	retval = junction_is_xattr_present(fd, pathname, JUNCTION_XATTR_NAME_NFS);
 	if (retval != FEDFS_OK)
 		goto out_close;
 
-	retval = junction_is_xattr_present(fd, pathname, FEDFS_XATTR_NAME_FSNUUID);
-	if (retval != FEDFS_OK)
-		goto out_close;
+	(void)close(fd);
 
-	retval = junction_is_xattr_present(fd, pathname, FEDFS_XATTR_NAME_NSDB);
-	if (retval != FEDFS_OK)
-		goto out_close;
-	
-	retval = junction_is_xattr_present(fd, pathname, FEDFS_XATTR_NAME_PORT);
+	return fedfs_is_junction_xml(pathname);
 
 out_close:
 	(void)close(fd);
diff --git a/src/libjunction/junction-internal.h b/src/libjunction/junction-internal.h
index 5be355d..a2c35c0 100644
--- a/src/libjunction/junction-internal.h
+++ b/src/libjunction/junction-internal.h
@@ -26,6 +26,48 @@ 
 #ifndef _FEDFS_JUNCTION_INTERNAL_H_
 #define _FEDFS_JUNCTION_INTERNAL_H_
 
+#include <libxml/tree.h>
+#include <libxml/xpath.h>
+
+/**
+ ** Names of extended attributes that store junction data
+ **/
+
+/**
+ * Name of extended attribute containing saved mode bits
+ */
+#define JUNCTION_XATTR_NAME_MODE	"trusted.junction.mode"
+
+/**
+ * Name of extended attribute containing junction type
+ */
+#define JUNCTION_XATTR_NAME_TYPE	"trusted.junction.type"
+
+/**
+ * Name of extended attribute containing NFS-related junction data
+ */
+#define JUNCTION_XATTR_NAME_NFS		"trusted.junction.nfs"
+
+
+/**
+ ** Names of XML elements and attributes that represent junction data
+ **/
+
+/**
+ * Tag name of root element of a junction XML document
+ */
+#define JUNCTION_XML_ROOT_TAG		(const xmlChar *)"junction"
+
+/**
+ * Tag name of fileset element of a junction XML document
+ */
+#define JUNCTION_XML_FILESET_TAG	(const xmlChar *)"fileset"
+
+
+/**
+ ** Junction helper functions
+ **/
+
 FedFsStatus	 junction_open_path(const char *pathname, int *fd);
 FedFsStatus	 junction_is_directory(int fd, const char *path);
 FedFsStatus	 junction_is_sticky_bit_set(int fd, const char *path);
@@ -42,5 +84,24 @@  FedFsStatus	 junction_remove_xattr(int fd, const char *pathname,
 			const char *name);
 FedFsStatus	 junction_save_mode(const char *pathname);
 FedFsStatus	 junction_restore_mode(const char *pathname);
+FedFsStatus	 junction_add_type(const char *pathname, const char *type);
+FedFsStatus	 junction_remove_type(const char *pathname);
+
+
+/**
+ ** XML helper functions
+ **/
+
+_Bool		 junction_xml_is_empty(const xmlChar *content);
+_Bool		 junction_xml_match_node_name(xmlNodePtr node,
+			const xmlChar *name);
+_Bool		 junction_xml_get_int_attribute(xmlNodePtr node,
+			const xmlChar *attrname, int *value);
+void		 junction_xml_set_int_attribute(xmlNodePtr node,
+			const xmlChar *attrname, int value);
+FedFsStatus	 junction_xml_parse(const char *pathname, const char *name,
+			xmlDocPtr *doc);
+FedFsStatus	 junction_xml_write(const char *pathname, const char *name,
+			xmlDocPtr doc);
 
 #endif	/* !_FEDFS_JUNCTION_INTERNAL_H_ */
diff --git a/src/libjunction/junction.c b/src/libjunction/junction.c
index 832c19f..baf7a90 100644
--- a/src/libjunction/junction.c
+++ b/src/libjunction/junction.c
@@ -47,12 +47,6 @@ 
 #include "xlog.h"
 
 /**
- * Name of extended attribute containing saved mode bits
- */
-#define JUNCTION_XATTR_NAME_MODE	"trusted.junction.mode"
-
-
-/**
  * Open a file system object
  *
  * @param pathname NUL-terminated C string containing pathname of an object
@@ -471,3 +465,56 @@  out:
 	(void)close(fd);
 	return retval;
 }
+
+/**
+ * Add the TYPE xattr
+ *
+ * @param pathname NUL-terminated C string containing pathname of a directory
+ * @param type NUL-terminated C string containing the contents of the new xattr
+ * @return a FedFsStatus code
+ *
+ * @note Access to trusted attributes requires CAP_SYS_ADMIN.
+ *
+ * The TYPE xattr is read by local file servers to know when they should
+ * perform junction resolution.
+ */
+FedFsStatus
+junction_add_type(const char *pathname, const char *type)
+{
+	FedFsStatus retval;
+	int fd;
+
+	retval = junction_open_path(pathname, &fd);
+	if (retval != FEDFS_OK)
+		return retval;
+
+	retval = junction_set_xattr(fd, pathname, JUNCTION_XATTR_NAME_TYPE,
+						type, strlen(type) + 1);
+
+	(void)close(fd);
+	return retval;
+}
+
+/**
+ * Remove the TYPE xattr
+ *
+ * @param pathname NUL-terminated C string containing pathname of a directory
+ * @return a FedFsStatus code
+ *
+ * @note Access to trusted attributes requires CAP_SYS_ADMIN.
+ */
+FedFsStatus
+junction_remove_type(const char *pathname)
+{
+	FedFsStatus retval;
+	int fd;
+
+	retval = junction_open_path(pathname, &fd);
+	if (retval != FEDFS_OK)
+		return retval;
+
+	retval = junction_remove_xattr(fd, pathname, JUNCTION_XATTR_NAME_TYPE);
+
+	(void)close(fd);
+	return retval;
+}
diff --git a/src/libjunction/xml.c b/src/libjunction/xml.c
new file mode 100644
index 0000000..bc77fdc
--- /dev/null
+++ b/src/libjunction/xml.c
@@ -0,0 +1,244 @@ 
+/**
+ * @file src/libjunction/xml.c
+ * @brief Common utilities for managing junction XML
+ */
+
+/*
+ * Copyright 2011 Oracle.  All rights reserved.
+ *
+ * This file is part of fedfs-utils.
+ *
+ * fedfs-utils is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2.0 as
+ * published by the Free Software Foundation.
+ *
+ * fedfs-utils 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 General Public License version 2.0 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2.0 along with fedfs-utils.  If not, see:
+ *
+ *	http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "fedfs.h"
+#include "junction.h"
+#include "junction-internal.h"
+#include "xlog.h"
+
+/**
+ * Predicate: is element content empty?
+ *
+ * @param content element content to test
+ * @return true if content is empty
+ */
+_Bool
+junction_xml_is_empty(const xmlChar *content)
+{
+	return content == NULL || *content == '\0';
+}
+
+/**
+ * Match an XML parse tree node by its name
+ *
+ * @param node pointer to a node in an XML parse tree
+ * @param name NUL-terminated C string containing name to match
+ * @return true if "node" is named "name"
+ */
+_Bool
+junction_xml_match_node_name(xmlNodePtr node, const xmlChar *name)
+{
+	return (node->type == XML_ELEMENT_NODE) &&
+		(xmlStrcmp(node->name, name) == 0);
+}
+
+/**
+ * Read attribute into an integer
+ *
+ * @param node pointer to a node in an XML parse tree
+ * @param attrname NUL-terminated C string containing attribute name
+ * @param value OUT: attribute's value converted to an integer 
+ * @return true if attribute "attrname" has a valid integer value
+ */
+_Bool
+junction_xml_get_int_attribute(xmlNodePtr node, const xmlChar *attrname,
+		int *value)
+{
+	char *endptr;
+	_Bool retval;
+	char *prop;
+	long tmp;
+
+	retval = false;
+	prop = (char *)xmlGetProp(node, attrname);
+	if (prop == NULL)
+		goto out;
+
+	errno = 0;
+	tmp = strtol(prop, &endptr, 10);
+	if (errno != 0 || *endptr != '\0' || tmp > INT32_MAX || tmp < INT32_MIN)
+		goto out;
+
+	*value = (int)tmp;
+	retval = true;
+
+out:
+	xmlFree(prop);
+	return retval;
+}
+
+/**
+ * Set attribute to an integer
+ *
+ * @param node pointer to a node in an XML parse tree
+ * @param attrname NUL-terminated C string containing attribute name
+ * @param value integer value to set
+ * @return true if attribute "attrname" has a valid integer value
+ */
+void
+junction_xml_set_int_attribute(xmlNodePtr node, const xmlChar *attrname,
+		int value)
+{
+	char buf[16];
+
+	snprintf(buf, sizeof(buf), "%d", value);
+	xmlSetProp(node, attrname, (const xmlChar *)buf);
+}
+
+/**
+ * Parse XML document in a buffer into an XML document tree
+ *
+ * @param pathname NUL-terminated C string containing pathname of a directory
+ * @param name NUL-terminated C string containing name of xattr to replace
+ * @param buf opaque byte array containing XML to parse
+ * @param len size of "buf" in bytes
+ * @param doc OUT: an XML parse tree containing junction XML
+ * @return a FedFsStatus code
+ *
+ * If junction_parse_xml_buf() returns success, caller must free "*doc"
+ * using xmlFreeDoc(3).
+ *
+ * @note Access to trusted attributes requires CAP_SYS_ADMIN.
+ */
+static FedFsStatus
+junction_parse_xml_buf(const char *pathname, const char *name,
+		void *buf, size_t len, xmlDocPtr *doc)
+{
+	xmlDocPtr tmp;
+
+	tmp = xmlParseMemory(buf, (int)len);
+	if (tmp == NULL) {
+		xlog(D_GENERAL, "Failed to parse XML in %s(%s)\n",
+			pathname, name);
+		return FEDFS_ERR_SVRFAULT;
+	}
+
+	*doc = tmp;
+	return FEDFS_OK;
+}
+
+/**
+ * Read an XML document from an extended attribute into an XML document tree
+ *
+ * @param pathname NUL-terminated C string containing pathname of a directory
+ * @param fd an open file descriptor
+ * @param name NUL-terminated C string containing name of xattr to replace
+ * @param doc OUT: an XML parse tree containing junction XML
+ * @return a FedFsStatus code
+ *
+ * If junction_parse_xml_read() returns success, caller must free "*doc"
+ * using xmlFreeDoc(3).
+ *
+ * @note Access to trusted attributes requires CAP_SYS_ADMIN.
+ */
+static FedFsStatus
+junction_parse_xml_read(const char *pathname, int fd, const char *name,
+		xmlDocPtr *doc)
+{
+	FedFsStatus retval;
+	void *buf = NULL;
+	size_t len;
+
+	retval = junction_get_xattr(fd, pathname, name, &buf, &len);
+	if (retval != FEDFS_OK)
+		return retval;
+
+	xlog(D_CALL, "%s: XML document contained in junction:\n%.*s",
+		__func__, len, buf);
+
+	retval = junction_parse_xml_buf(pathname, name, buf, len, doc);
+
+	free(buf);
+	return retval;
+}
+
+/**
+ * Read an XML document from an extended attribute into an XML document tree
+ *
+ * @param pathname NUL-terminated C string containing pathname of a directory
+ * @param name NUL-terminated C string containing name of xattr to replace
+ * @param doc OUT: an XML parse tree containing junction XML
+ * @return a FedFsStatus code
+ *
+ * If junction_parse_xml() returns success, caller must free "*doc"
+ * using xmlFreeDoc(3).
+ *
+ * @note Access to trusted attributes requires CAP_SYS_ADMIN.
+ */
+FedFsStatus
+junction_xml_parse(const char *pathname, const char *name, xmlDocPtr *doc)
+{
+	FedFsStatus retval;
+	int fd;
+
+	retval = junction_open_path(pathname, &fd);
+	if (retval != FEDFS_OK)
+		return retval;
+
+	retval = junction_parse_xml_read(pathname, fd, name, doc);
+
+	(void)close(fd);
+	return retval;
+}
+
+/**
+ * Write an XML document into an extended attribute
+ *
+ * @param pathname NUL-terminated C string containing pathname of a directory
+ * @param name NUL-terminated C string containing name of xattr to replace
+ * @param doc an XML parse tree containing junction XML
+ * @return a FedFsStatus code
+ *
+ * @note Access to trusted attributes requires CAP_SYS_ADMIN.
+ */
+FedFsStatus
+junction_xml_write(const char *pathname, const char *name, xmlDocPtr doc)
+{
+	xmlChar *buf = NULL;
+	FedFsStatus retval;
+	int fd, len;
+
+	retval = junction_open_path(pathname, &fd);
+	if (retval != FEDFS_OK)
+		return retval;
+
+	xmlIndentTreeOutput = 1;
+	xmlDocDumpFormatMemoryEnc(doc, &buf, &len, "UTF-8", 1);
+	retval = junction_set_xattr(fd, pathname, name, buf, len);
+	xmlFree(buf);
+
+	(void)close(fd);
+	return retval;
+}
diff --git a/src/nsdbc/Makefile.am b/src/nsdbc/Makefile.am
index a52d91c..bf0e057 100644
--- a/src/nsdbc/Makefile.am
+++ b/src/nsdbc/Makefile.am
@@ -28,7 +28,7 @@  sbin_PROGRAMS		= nsdb-annotate nsdb-describe nsdb-list \
 			  nsdb-delete-nsdb \
 			  nsdb-create-fsn nsdb-delete-fsn nsdb-resolve-fsn \
 			  nsdb-create-fsl nsdb-delete-fsl nsdb-update-fsl
-LDADD			= $(LIBLDAP) $(LIBLBER) \
+LDADD			= $(LIBLDAP) $(LIBLBER) $(LIBXML2) \
 			  $(LIBSQLITE3) $(LIBIDN) $(LIBUUID) \
 			  $(top_builddir)/src/libnsdb/libnsdb.la \
 			  $(top_builddir)/src/libxlog/libxlog.la \
diff --git a/src/nsdbparams/Makefile.am b/src/nsdbparams/Makefile.am
index b21220e..7b7911a 100644
--- a/src/nsdbparams/Makefile.am
+++ b/src/nsdbparams/Makefile.am
@@ -26,7 +26,7 @@ 
 noinst_HEADERS		= nsdbparams.h
 sbin_PROGRAMS		= nsdbparams
 nsdbparams_SOURCES	= delete.c list.c main.c show.c update.c
-LDADD			= $(LIBLDAP) $(LIBLBER) \
+LDADD			= $(LIBLDAP) $(LIBLBER) $(LIBXML2) \
 			  $(LIBSQLITE3) $(LIBIDN) $(LIBUUID) $(LIBCAP) \
 			  $(top_builddir)/src/libnsdb/libnsdb.la \
 			  $(top_builddir)/src/libjunction/libjunction.la \
diff --git a/src/resolve-junction/Makefile.am b/src/resolve-junction/Makefile.am
index 323a1d9..51ca299 100644
--- a/src/resolve-junction/Makefile.am
+++ b/src/resolve-junction/Makefile.am
@@ -27,7 +27,7 @@  noinst_HEADERS		= privilege.h
 
 sbin_PROGRAMS		= resolve-junction
 resolve_junction_SOURCES = main.c privilege.c privilege.h
-LDADD			= $(LIBLDAP) $(LIBLBER) \
+LDADD			= $(LIBLDAP) $(LIBLBER) $(LIBXML2) \
 			  $(LIBSQLITE3) $(LIBIDN) $(LIBUUID) $(LIBCAP) \
 			  $(top_builddir)/src/libadmin/libadmin.la \
 			  $(top_builddir)/src/libnsdb/libnsdb.la \