Patchwork [7/7] libjunction: Add support for nfs-basic junctions

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

Comments

Chuck Lever - Jan. 4, 2012, 9:07 p.m.
Now that we have some example XML support in libjunction, we can begin
to sketch in support for NFS junctions.  Instead of storing a FedFS
fileset name which is resolved to a set of fileset locations, an NFS
junction stores the actual set of fileset locations.

The XML for an NFS junction looks something like this:

  <?xml version="1.0" encoding="UTF-8"?>
  <junction>
    <fileset>

      <location>
        <host>fileserver.example.net</host>
        <path>
          <component>foo</component>
          <component>bar</component>
          <component>baz</component>
        </path>
        <version major="4" minor="0" />
        <currency>-1</currency>
        <genflags writable="false" going="false" split="true" />
        <transflags rdma="true" />
        <class simul="0"
               handle="0"
               fileid="0"
               writever="0"
               change="0"
               readdir="0" />
        <read rank="0" order="0" />
        <write rank="0" order="0">
        <flags varsub="false" />
        <validfor>0</validfor>
      </location>

      ....

    </fileset>
  </junction>

Basically the <location> element is a FedFS NFS fileset location
record, expressed in XML.  A list of these is parsed out of a junction
XML document and then presented to, say, mountd.

Although NFS supports both fs_locations and fs_locations_info, there
is only one type of NFS fileset location record.  This is exactly the
same kind of information mountd would get if it resolved a FedFS FSN.
Non-3530 information can be discarded to construct a regular
fs_locations in response to an NFSv4.0 client.

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

 src/include/junction.h              |   77 ++
 src/libjunction/Makefile.am         |    3 
 src/libjunction/junction-internal.h |   11 
 src/libjunction/locations.c         |  131 +++
 src/libjunction/nfs.c               | 1681 +++++++++++++++++++++++++++++++++++
 src/libjunction/xml.c               |  155 +++
 6 files changed, 2056 insertions(+), 2 deletions(-)
 create mode 100644 src/libjunction/locations.c
 create mode 100644 src/libjunction/nfs.c

Patch

diff --git a/src/include/junction.h b/src/include/junction.h
index de2f496..f0d041f 100644
--- a/src/include/junction.h
+++ b/src/include/junction.h
@@ -26,8 +26,85 @@ 
 #ifndef _FEDFS_JUNCTION_H_
 #define _FEDFS_JUNCTION_H_
 
+#include <stdint.h>
 #include "nsdb.h"
 
+/**
+ * Contains NFS fileset location information
+ *
+ * Each of these represents one server:/rootpath pair.  The NFS
+ * implementation can coalesce multiple pairs into a single
+ * fs_location4 result if jfl_rootpath is the same across
+ * multiple servers.
+ *
+ * The nfl_server field can contain either one presentation format
+ * IP address or one DNS hostname.
+ *
+ * See Section 11.9 and 11.10 of RFC 5661 or section 4.2.2.3 and
+ * 4.2.2.4 of the NSDB protocol draft for details.
+ */
+
+struct nfs_fsloc {
+	struct nfs_fsloc	 *nfl_next;
+
+	char			 *nfl_hostname;
+	uint16_t		  nfl_hostport;
+	char			**nfl_rootpath;
+
+	struct {
+		_Bool		  nfl_varsub;
+	} nfl_flags;
+	int32_t			  nfl_currency;
+	int32_t			  nfl_validfor;
+
+	struct {
+		_Bool		  nfl_writable, nfl_going, nfl_split;
+	} nfl_genflags;
+	struct {
+		_Bool		  nfl_rdma;
+	} nfl_transflags;
+	struct {
+		uint8_t		  nfl_simul, nfl_handle, nfl_fileid;
+		uint8_t		  nfl_writever, nfl_change, nfl_readdir;
+		uint8_t		  nfl_readrank, nfl_writerank;
+		uint8_t		  nfl_readorder, nfl_writeorder;
+	} nfl_info;
+
+	int32_t			  nfl_majorver, nfl_minorver;
+	int32_t			  nfl_ttl;
+};
+
+
+/**
+ ** NFS location data management functions
+ **/
+
+void		  nfs_free_location(struct nfs_fsloc *location);
+void		  nfs_free_locations(struct nfs_fsloc *locations);
+struct nfs_fsloc *nfs_new_location(void);
+
+__attribute_malloc__
+char		**nfs_dup_string_array(char **array);
+void		  nfs_free_string_array(char **array);
+
+
+/**
+ ** NFS junction management functions
+ **/
+
+FedFsStatus	 nfs_delete_junction(const char *pathname);
+FedFsStatus	 nfs_add_junction(const char *pathname,
+				struct nfs_fsloc *locations);
+FedFsStatus	 nfs_get_locations(const char *pathname,
+				struct nfs_fsloc **locations);
+FedFsStatus	 nfs_is_prejunction(const char *pathname);
+FedFsStatus	 nfs_is_junction(const char *pathname);
+
+
+/**
+ ** FedFS junction management functions
+ **/
+
 FedFsStatus	 fedfs_delete_junction(const char *pathname);
 FedFsStatus	 fedfs_add_junction(const char *pathname, const char *uuid,
 				const nsdb_t host);
diff --git a/src/libjunction/Makefile.am b/src/libjunction/Makefile.am
index 9d91fef..4898d01 100644
--- a/src/libjunction/Makefile.am
+++ b/src/libjunction/Makefile.am
@@ -26,7 +26,8 @@ 
 noinst_HEADERS		= junction-internal.h
 
 noinst_LTLIBRARIES	= libjunction.la
-libjunction_la_SOURCES	= export-cache.c fedfs.c junction.c xml.c
+libjunction_la_SOURCES	= export-cache.c fedfs.c junction.c \
+			  locations.c nfs.c xml.c
 
 CLEANFILES		= cscope.in.out cscope.out cscope.po.out *~
 DISTCLEANFILES		= Makefile.in
diff --git a/src/libjunction/junction-internal.h b/src/libjunction/junction-internal.h
index a2c35c0..f362d0d 100644
--- a/src/libjunction/junction-internal.h
+++ b/src/libjunction/junction-internal.h
@@ -95,10 +95,21 @@  FedFsStatus	 junction_remove_type(const char *pathname);
 _Bool		 junction_xml_is_empty(const xmlChar *content);
 _Bool		 junction_xml_match_node_name(xmlNodePtr node,
 			const xmlChar *name);
+xmlNodePtr	 junction_xml_find_child_by_name(xmlNodePtr parent,
+			const xmlChar *name);
+_Bool		 junction_xml_get_bool_attribute(xmlNodePtr node,
+			const xmlChar *attrname, _Bool *value);
+void		 junction_xml_set_bool_attribute(xmlNodePtr node,
+			const xmlChar *attrname, _Bool value);
+_Bool		 junction_xml_get_u8_attribute(xmlNodePtr node,
+			const xmlChar *attrname, uint8_t *value);
 _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);
+_Bool		 junction_xml_get_int_content(xmlNodePtr node, int *value);
+xmlNodePtr	 junction_xml_set_int_content(xmlNodePtr parent,
+			const xmlChar *name, int value);
 FedFsStatus	 junction_xml_parse(const char *pathname, const char *name,
 			xmlDocPtr *doc);
 FedFsStatus	 junction_xml_write(const char *pathname, const char *name,
diff --git a/src/libjunction/locations.c b/src/libjunction/locations.c
new file mode 100644
index 0000000..58e5543
--- /dev/null
+++ b/src/libjunction/locations.c
@@ -0,0 +1,131 @@ 
+/**
+ * @file src/libjunction/locations.c
+ * @brief Utility functions to manage NFS locations data
+ */
+
+/*
+ * 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 <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "junction.h"
+
+/**
+ * Free an array of NUL-terminated C strings
+ *
+ * @param array array of pointers to C strings
+ */
+void
+nfs_free_string_array(char **array)
+{
+	unsigned int i;
+
+	if (array == NULL)
+		return;
+	for (i = 0; array[i] != NULL; i++)
+		free(array[i]);
+	free(array);
+}
+
+/**
+ * Duplicate an array of NUL-terminated C strings
+ *
+ * @param array array of pointers to C strings
+ * @return freshly allocated array of points to C strings, or NULL
+ *
+ * Caller must free the returned array with nfs_free_string_array()
+ */
+__attribute_malloc__ char **
+nfs_dup_string_array(char **array)
+{
+	unsigned int size, i;
+	char **result;
+
+	if (array == NULL)
+		return NULL;
+
+	for (size = 0; array[size] != NULL; size++);
+
+	result = calloc(size + 1, sizeof(char *));
+	if (result == NULL)
+		return NULL;
+	for (i = 0; i < size; i++) {
+		result[i] = strdup(array[i]);
+		if (result[i] == NULL) {
+			nfs_free_string_array(result);
+			return NULL;
+		}
+	}
+	return result;
+}
+
+/**
+ * Free a single NFS location
+ *
+ * @param location pointer to nfs_fsloc data
+ */
+void
+nfs_free_location(struct nfs_fsloc *location)
+{
+	nfs_free_string_array(location->nfl_rootpath);
+	free(location->nfl_hostname);
+	free(location);
+}
+
+/**
+ * Free a list of NFS locations
+ *
+ * @param locations pointer to list of one or more locations
+ */
+void
+nfs_free_locations(struct nfs_fsloc *locations)
+{
+	struct nfs_fsloc *fsloc;
+
+	while (locations != NULL) {
+		fsloc = locations;
+		locations = fsloc->nfl_next;
+		nfs_free_location(fsloc);
+	}
+}
+
+/**
+ * Allocate a fresh nfs_fsloc structure
+ *
+ * @return pointer to new empty nfs_fsloc data structure
+ *
+ * Caller must free returned locations with nfs_free_location().
+ */
+struct nfs_fsloc *
+nfs_new_location(void)
+{
+	return calloc(1, sizeof(struct nfs_fsloc));
+}
diff --git a/src/libjunction/nfs.c b/src/libjunction/nfs.c
new file mode 100644
index 0000000..f47d848
--- /dev/null
+++ b/src/libjunction/nfs.c
@@ -0,0 +1,1681 @@ 
+/**
+ * @file src/libjunction/nfs.c
+ * @brief Create, delete, and read NFS junctions on the local file system
+ */
+
+/*
+ * 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
+ */
+
+/*
+ * An NFS junction is a list of NFS FSLs, represented in a well-formed XML
+ * document:
+ *
+ * <?xml version="1.0" encoding="UTF-8"?>
+ * <junction>
+ *   <fileset>
+ *     <location>
+ *       <host name="fileserver.example.net" port="2049" />
+ *       <path>
+ *         <component>foo</component>
+ *         <component>bar</component>
+ *         <component>baz</component>
+ *       </path>
+ *       <version major="4" minor="0" />
+ *       <currency>-1</currency>
+ *       <genflags writable="false" going="false" split="true" />
+ *       <transflags rdma="true" />
+ *       <class simul="0" handle="0" fileid="0"
+ *              writever="0" change="0" readdir="0" />
+ *       <read rank="0" order="0" />
+ *       <write rank="0" order="0" />
+ *       <flags varsub="false" />
+ *       <validfor>0</validfor>
+ *     </location>
+ *
+ *     ....
+ *
+ *   </fileset>
+ * </junction>
+ *
+ * NFS 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>
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "fedfs.h"
+#include "nsdb.h"
+#include "junction.h"
+#include "junction-internal.h"
+#include "xlog.h"
+
+#ifndef NFS_PORT
+#define NFS_PORT	(2049)
+#endif
+
+/**
+ * Tag name of NFS location element of a junction XML document
+ */
+#define NFS_XML_LOCATION_TAG		(const xmlChar *)"location"
+
+/**
+ * Tag name of host child element of an NFS location element
+ */
+#define NFS_XML_HOST_TAG		(const xmlChar *)"host"
+
+/**
+ * Name of hostname attribute of a host element
+ */
+#define NFS_XML_HOST_NAME_ATTR		(const xmlChar *)"name"
+
+/**
+ * Name of IP port attribute of a host element
+ */
+#define NFS_XML_HOST_PORT_ATTR		(const xmlChar *)"port"
+
+/**
+ * Tag name of path child element of an NFS location element
+ */
+#define NFS_XML_PATH_TAG		(const xmlChar *)"path"
+
+/**
+ * Tag name of component child element of a path element
+ */
+#define NFS_XML_COMPONENT_TAG		(const xmlChar *)"component"
+
+/**
+ * Tag name of version child element of an NFS location element
+ */
+#define NFS_XML_VERSION_TAG		(const xmlChar *)"version"
+
+/**
+ * Name of major version attribute of a version element
+ */
+#define NFS_XML_VERSION_MAJOR_ATTR	(const xmlChar *)"major"
+
+/**
+ * Name of minor version attribute of a version element
+ */
+#define NFS_XML_VERSION_MINOR_ATTR	(const xmlChar *)"minor"
+
+/**
+ * Tag name of currency child element of an NFS location element
+ */
+#define NFS_XML_CURRENCY_TAG		(const xmlChar *)"currency"
+
+/**
+ * Tag name of genflags child element of an NFS location element
+ */
+#define NFS_XML_GENFLAGS_TAG		(const xmlChar *)"genflags"
+
+/**
+ * Name of writable attribute of a genflags element
+ */
+#define NFS_XML_GENFLAGS_WRITABLE_ATTR	(const xmlChar *)"writable"
+
+/**
+ * Name of going attribute of a genflags element
+ */
+#define NFS_XML_GENFLAGS_GOING_ATTR	(const xmlChar *)"going"
+
+/**
+ * Name of split attribute of a genflags element
+ */
+#define	NFS_XML_GENFLAGS_SPLIT_ATTR	(const xmlChar *)"split"
+
+/**
+ * Tag name of transflags child element of an NFS location element
+ */
+#define NFS_XML_TRANSFLAGS_TAG		(const xmlChar *)"transflags"
+
+/**
+ * Name of rdma attribute of a transflags element
+ */
+#define NFS_XML_TRANSFLAGS_RDMA_ATTR	(const xmlChar *)"rdma"
+
+/**
+ * Tag name of class child element of an NFS location element
+ */
+#define NFS_XML_CLASS_TAG		(const xmlChar *)"class"
+
+/**
+ * Name of simul attribute of a class element
+ */
+#define NFS_XML_CLASS_SIMUL_ATTR	(const xmlChar *)"simul"
+
+/**
+ * Name of handle attribute of a class element
+ */
+#define NFS_XML_CLASS_HANDLE_ATTR	(const xmlChar *)"handle"
+
+/**
+ * Name of fileid attribute of a class element
+ */
+#define NFS_XML_CLASS_FILEID_ATTR	(const xmlChar *)"fileid"
+
+/**
+ * Name of writever attribute of a class element
+ */
+#define NFS_XML_CLASS_WRITEVER_ATTR	(const xmlChar *)"writever"
+
+/**
+ * Name of change attribute of a class element
+ */
+#define NFS_XML_CLASS_CHANGE_ATTR	(const xmlChar *)"change"
+
+/**
+ * Name of readdir attribute of a class element
+ */
+#define NFS_XML_CLASS_READDIR_ATTR	(const xmlChar *)"readdir"
+
+/**
+ * Tag name of read child element of an NFS location element
+ */
+#define NFS_XML_READ_TAG		(const xmlChar *)"read"
+
+/**
+ * Name of rank attribute of a read element
+ */
+#define NFS_XML_READ_RANK_ATTR		(const xmlChar *)"rank"
+
+/**
+ * Name of order attribute of a read element
+ */
+#define NFS_XML_READ_ORDER_ATTR		(const xmlChar *)"order"
+
+/**
+ * Tag name of write attribute of an NFS location element
+ */
+#define NFS_XML_WRITE_TAG		(const xmlChar *)"write"
+
+/**
+ * Name of rank attribute of a write element
+ */
+#define NFS_XML_WRITE_RANK_ATTR		(const xmlChar *)"rank"
+
+/**
+ * Name of order attribute of a write element
+ */
+#define NFS_XML_WRITE_ORDER_ATTR	(const xmlChar *)"order"
+
+/**
+ * Tag name of flags child element of an NFS location element
+ */
+#define NFS_XML_FLAGS_TAG		(const xmlChar *)"flags"
+
+/**
+ * Name of varsub attribute of a flags element
+ */
+#define NFS_XML_FLAGS_VARSUB_ATTR	(const xmlChar *)"varsub"
+
+/**
+ * Tag name of a validfor child element of an NFS location element
+ */
+#define NFS_XML_VALIDFOR_TAG		(const xmlChar *)"validfor"
+
+/**
+ * Tag name of a ttl child element of an NFS location element
+ */
+#define NFS_XML_TTL_TAG			(const xmlChar *)"ttl"
+
+/**
+ * XPath path to NFS location elements in a junction document
+ */
+#define NFS_XML_LOCATION_XPATH		(const xmlChar *)	\
+						"/junction/fileset/location"
+
+
+/**
+ * Remove all NFS-related xattrs from a directory
+ *
+ * @param pathname NUL-terminated C string containing pathname of a directory
+ * @return a FedFsStatus code
+ *
+ * @note Access to trusted attributes requires CAP_SYS_ADMIN.
+ */
+static FedFsStatus
+nfs_remove_locations(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_NFS);
+
+	(void)close(fd);
+	return retval;
+}
+
+/**
+ * Add a "host" child to a "location" element
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param parent parent element to which to add "host" child
+ * @param fsloc NFS location containing host information to add
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_location_host_xml(const char *pathname, xmlNodePtr parent,
+		struct nfs_fsloc *fsloc)
+{
+	uint16_t port = fsloc->nfl_hostport;
+	xmlNodePtr new;
+
+	new = xmlNewTextChild(parent, NULL, NFS_XML_HOST_TAG, NULL);
+	if (new == NULL) {
+		xlog(D_GENERAL, "%s: Failed to add host element for %s",
+			__func__, pathname);
+		return FEDFS_ERR_SVRFAULT;
+	}
+
+	xmlSetProp(new, NFS_XML_HOST_NAME_ATTR,
+			(const xmlChar *)fsloc->nfl_hostname);
+	if (port != NFS_PORT && port != 0)
+		junction_xml_set_int_attribute(new, NFS_XML_HOST_PORT_ATTR,
+									port);
+
+	return FEDFS_OK;
+}
+
+/**
+ * Add a "path" child to a "location" element
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param parent parent element to which to add "host" child
+ * @param fsloc NFS location containing host information to add
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_location_path_xml(const char *pathname, xmlNodePtr parent,
+		struct nfs_fsloc *fsloc)
+{
+	xmlNodePtr new;
+	int i;
+
+	new = xmlNewTextChild(parent, NULL, NFS_XML_PATH_TAG, NULL);
+	if (new == NULL) {
+		xlog(D_GENERAL, "%s: Failed to add path element for %s",
+			__func__, pathname);
+		return FEDFS_ERR_SVRFAULT;
+	}
+
+	for (i = 0; fsloc->nfl_rootpath[i] != NULL; i++) {
+		xmlNodePtr component;
+
+		component = xmlNewTextChild(new , NULL,
+						NFS_XML_COMPONENT_TAG,
+						(const xmlChar *)
+						fsloc->nfl_rootpath[i]);
+		if (component == NULL) {
+			xlog(D_GENERAL, "%s: Failed to add component "
+					"element for %s",
+				__func__, pathname);
+			return FEDFS_ERR_SVRFAULT;
+		}
+	}
+
+	return FEDFS_OK;
+}
+
+/**
+ * Add a "version" child to a "location" element
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param parent parent element to which to add "host" child
+ * @param fsloc NFS location containing host information to add
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_location_version_xml(const char *pathname, xmlNodePtr parent,
+		struct nfs_fsloc *fsloc)
+{
+	xmlNodePtr new;
+
+	new = xmlNewTextChild(parent, NULL, NFS_XML_VERSION_TAG, NULL);
+	if (new == NULL) {
+		xlog(D_GENERAL, "%s: Failed to add version element for %s",
+			__func__, pathname);
+		return FEDFS_ERR_SVRFAULT;
+	}
+
+	junction_xml_set_int_attribute(new, NFS_XML_VERSION_MAJOR_ATTR,
+							fsloc->nfl_majorver);
+	junction_xml_set_int_attribute(new, NFS_XML_VERSION_MINOR_ATTR,
+							fsloc->nfl_minorver);
+
+	return FEDFS_OK;
+}
+
+/**
+ * Add a "currency" child to a "location" element
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param parent parent element to which to add "host" child
+ * @param fsloc NFS location containing host information to add
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_location_currency_xml(__attribute__((unused)) const char *pathname,
+		xmlNodePtr parent, struct nfs_fsloc *fsloc)
+{
+	if (junction_xml_set_int_content(parent, NFS_XML_CURRENCY_TAG,
+						fsloc->nfl_currency) == NULL)
+		return FEDFS_ERR_SVRFAULT;
+	return FEDFS_OK;
+}
+
+/**
+ * Add a "genflags" child to a "location" element
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param parent parent element to which to add "host" child
+ * @param fsloc NFS location containing host information to add
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_location_genflags_xml(const char *pathname, xmlNodePtr parent,
+		struct nfs_fsloc *fsloc)
+{
+	xmlNodePtr new;
+
+	new = xmlNewTextChild(parent, NULL, NFS_XML_GENFLAGS_TAG, NULL);
+	if (new == NULL) {
+		xlog(D_GENERAL, "%s: Failed to add genflags element for %s",
+			__func__, pathname);
+		return FEDFS_ERR_SVRFAULT;
+	}
+
+	junction_xml_set_bool_attribute(new, NFS_XML_GENFLAGS_WRITABLE_ATTR,
+					fsloc->nfl_genflags.nfl_writable);
+	junction_xml_set_bool_attribute(new, NFS_XML_GENFLAGS_GOING_ATTR,
+					fsloc->nfl_genflags.nfl_going);
+	junction_xml_set_bool_attribute(new, NFS_XML_GENFLAGS_SPLIT_ATTR,
+					fsloc->nfl_genflags.nfl_split);
+
+	return FEDFS_OK;
+}
+
+/**
+ * Add a "transflags" child to a "location" element
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param parent parent element to which to add "host" child
+ * @param fsloc NFS location containing host information to add
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_location_transflags_xml(const char *pathname, xmlNodePtr parent,
+		struct nfs_fsloc *fsloc)
+{
+	xmlNodePtr new;
+
+	new = xmlNewTextChild(parent, NULL, NFS_XML_TRANSFLAGS_TAG, NULL);
+	if (new == NULL) {
+		xlog(D_GENERAL, "%s: Failed to add transflags element for %s",
+			__func__, pathname);
+		return FEDFS_ERR_SVRFAULT;
+	}
+
+	junction_xml_set_bool_attribute(new, NFS_XML_TRANSFLAGS_RDMA_ATTR,
+					fsloc->nfl_transflags.nfl_rdma);
+
+	return FEDFS_OK;
+}
+
+/**
+ * Add a "class" child to a "location" element
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param parent parent element to which to add "host" child
+ * @param fsloc NFS location containing host information to add
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_location_class_xml(const char *pathname, xmlNodePtr parent,
+		struct nfs_fsloc *fsloc)
+{
+	xmlNodePtr new;
+
+	new = xmlNewTextChild(parent, NULL, NFS_XML_CLASS_TAG, NULL);
+	if (new == NULL) {
+		xlog(D_GENERAL, "%s: Failed to add class element for %s",
+			__func__, pathname);
+		return FEDFS_ERR_SVRFAULT;
+	}
+
+	junction_xml_set_int_attribute(new, NFS_XML_CLASS_SIMUL_ATTR,
+						fsloc->nfl_info.nfl_simul);
+	junction_xml_set_int_attribute(new, NFS_XML_CLASS_HANDLE_ATTR,
+						fsloc->nfl_info.nfl_handle);
+	junction_xml_set_int_attribute(new, NFS_XML_CLASS_FILEID_ATTR,
+						fsloc->nfl_info.nfl_fileid);
+	junction_xml_set_int_attribute(new, NFS_XML_CLASS_WRITEVER_ATTR,
+						fsloc->nfl_info.nfl_writever);
+	junction_xml_set_int_attribute(new, NFS_XML_CLASS_CHANGE_ATTR,
+						fsloc->nfl_info.nfl_change);
+	junction_xml_set_int_attribute(new, NFS_XML_CLASS_READDIR_ATTR,
+						fsloc->nfl_info.nfl_readdir);
+
+	return FEDFS_OK;
+}
+
+/**
+ * Add a "read" child to a "location" element
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param parent parent element to which to add "host" child
+ * @param fsloc NFS location containing host information to add
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_location_read_xml(const char *pathname, xmlNodePtr parent,
+		struct nfs_fsloc *fsloc)
+{
+	xmlNodePtr new;
+
+	new = xmlNewTextChild(parent, NULL, NFS_XML_READ_TAG, NULL);
+	if (new == NULL) {
+		xlog(D_GENERAL, "%s: Failed to add read element for %s",
+			__func__, pathname);
+		return FEDFS_ERR_SVRFAULT;
+	}
+
+	junction_xml_set_int_attribute(new, NFS_XML_READ_RANK_ATTR,
+					fsloc->nfl_info.nfl_readrank);
+	junction_xml_set_int_attribute(new, NFS_XML_READ_ORDER_ATTR,
+					fsloc->nfl_info.nfl_readorder);
+
+	return FEDFS_OK;
+}
+
+/**
+ * Add a "write" child to a "location" element
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param parent parent element to which to add "host" child
+ * @param fsloc NFS location containing host information to add
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_location_write_xml(const char *pathname, xmlNodePtr parent,
+		struct nfs_fsloc *fsloc)
+{
+	xmlNodePtr new;
+
+	new = xmlNewTextChild(parent, NULL, NFS_XML_WRITE_TAG, NULL);
+	if (new == NULL) {
+		xlog(D_GENERAL, "%s: Failed to add write element for %s",
+			__func__, pathname);
+		return FEDFS_ERR_SVRFAULT;
+	}
+
+	junction_xml_set_int_attribute(new, NFS_XML_WRITE_RANK_ATTR,
+					fsloc->nfl_info.nfl_writerank);
+	junction_xml_set_int_attribute(new, NFS_XML_WRITE_ORDER_ATTR,
+					fsloc->nfl_info.nfl_writeorder);
+
+	return FEDFS_OK;
+}
+
+/**
+ * Add a "flags" child to a "location" element
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param parent parent element to which to add "host" child
+ * @param fsloc NFS location containing host information to add
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_location_flags_xml(const char *pathname, xmlNodePtr parent,
+		struct nfs_fsloc *fsloc)
+{
+	xmlNodePtr new;
+
+	new = xmlNewTextChild(parent, NULL, NFS_XML_FLAGS_TAG, NULL);
+	if (new == NULL) {
+		xlog(D_GENERAL, "%s: Failed to add flags element for %s",
+			__func__, pathname);
+		return FEDFS_ERR_SVRFAULT;
+	}
+
+	junction_xml_set_bool_attribute(new, NFS_XML_FLAGS_VARSUB_ATTR,
+					fsloc->nfl_flags.nfl_varsub);
+
+	return FEDFS_OK;
+}
+
+/**
+ * Add a "validfor" child to a "location" element
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param parent parent element to which to add "host" child
+ * @param fsloc NFS location containing host information to add
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_location_validfor_xml(__attribute__((unused)) const char *pathname,
+		xmlNodePtr parent, struct nfs_fsloc *fsloc)
+{
+	if (junction_xml_set_int_content(parent, NFS_XML_VALIDFOR_TAG,
+						fsloc->nfl_validfor) == NULL)
+		return FEDFS_ERR_SVRFAULT;
+	return FEDFS_OK;
+}
+
+/**
+ * Add a "validfor" child to a "location" element
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param parent parent element to which to add "host" child
+ * @param fsloc NFS location containing host information to add
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_location_ttl_xml(__attribute__((unused)) const char *pathname,
+		xmlNodePtr parent, struct nfs_fsloc *fsloc)
+{
+	if (junction_xml_set_int_content(parent, NFS_XML_TTL_TAG,
+						fsloc->nfl_ttl) == NULL)
+		return FEDFS_ERR_SVRFAULT;
+	return FEDFS_OK;
+}
+
+/**
+ * Construct and add one "location" element to a "fileset"
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param fileset fileset element of junction XML parse tree
+ * @param fsloc one NFS location to add
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_location_xml(const char *pathname, xmlNodePtr fileset,
+		struct nfs_fsloc *fsloc)
+{
+	FedFsStatus retval;
+	xmlNodePtr new;
+
+	new = xmlNewTextChild(fileset, NULL, NFS_XML_LOCATION_TAG, NULL);
+	if (new == NULL) {
+		xlog(D_GENERAL, "%s: Failed to add location element for %s",
+			__func__, pathname);
+		return FEDFS_ERR_SVRFAULT;
+	}
+
+	retval = nfs_location_host_xml(pathname, new, fsloc);
+	if (retval != FEDFS_OK)
+		return retval;
+	retval = nfs_location_path_xml(pathname, new, fsloc);
+	if (retval != FEDFS_OK)
+		return retval;
+	retval = nfs_location_version_xml(pathname, new, fsloc);
+	if (retval != FEDFS_OK)
+		return retval;
+	retval = nfs_location_currency_xml(pathname, new, fsloc);
+	if (retval != FEDFS_OK)
+		return retval;
+	retval = nfs_location_genflags_xml(pathname, new, fsloc);
+	if (retval != FEDFS_OK)
+		return retval;
+	retval = nfs_location_transflags_xml(pathname, new, fsloc);
+	if (retval != FEDFS_OK)
+		return retval;
+	retval = nfs_location_class_xml(pathname, new, fsloc);
+	if (retval != FEDFS_OK)
+		return retval;
+	retval = nfs_location_read_xml(pathname, new, fsloc);
+	if (retval != FEDFS_OK)
+		return retval;
+	retval = nfs_location_write_xml(pathname, new, fsloc);
+	if (retval != FEDFS_OK)
+		return retval;
+	retval = nfs_location_flags_xml(pathname, new, fsloc);
+	if (retval != FEDFS_OK)
+		return retval;
+	retval = nfs_location_validfor_xml(pathname, new, fsloc);
+	if (retval != FEDFS_OK)
+		return retval;
+	return nfs_location_ttl_xml(pathname, new, fsloc);
+}
+
+/**
+ * Construct and add a "fileset" element
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param root root element of junction XML parse tree
+ * @param fslocs list of NFS locations to add
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_fileset_xml(const char *pathname, xmlNodePtr root,
+		struct nfs_fsloc *fslocs)
+{
+	struct nfs_fsloc *next;
+	xmlNodePtr fileset;
+	FedFsStatus retval;
+
+	fileset = xmlNewTextChild(root, NULL, JUNCTION_XML_FILESET_TAG, NULL);
+	if (fileset == NULL) {
+		xlog(D_GENERAL, "%s: Failed to add fileset element for %s",
+			__func__, pathname);
+		return FEDFS_ERR_SVRFAULT;
+	}
+
+	for (next = fslocs; next != NULL; next = next->nfl_next) {
+		retval = nfs_location_xml(pathname, fileset, next);
+		if (retval != FEDFS_OK)
+			return retval;
+	}
+
+	return FEDFS_OK;
+}
+
+/**
+ * Construct NFS junction XML document from list of NFS locations
+ *
+ * @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 fslocs list of NFS locations to add
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_junction_xml(const char *pathname, xmlDocPtr doc,
+		struct nfs_fsloc *fslocs)
+{
+	xmlNodePtr root;
+
+	root = xmlNewNode(NULL, JUNCTION_XML_ROOT_TAG);
+	if (root == NULL) {
+		xlog(D_GENERAL, "%s: Failed to create root element for %s",
+			__func__, pathname);
+		return FEDFS_ERR_SVRFAULT;
+	}
+	(void)xmlDocSetRootElement(doc, root);
+
+	return nfs_fileset_xml(pathname, root, fslocs);
+}
+
+/**
+ * Write NFS locations information into an NFS 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 fslocs list of NFS locations to add
+ * @return a FedFsStatus code
+ *
+ * @note Access to trusted attributes requires CAP_SYS_ADMIN.
+ */
+static FedFsStatus
+nfs_write_junction(const char *pathname, xmlDocPtr doc,
+		struct nfs_fsloc *fslocs)
+{
+	FedFsStatus retval;
+
+	retval = nfs_junction_xml(pathname, doc, fslocs);
+	if (retval != FEDFS_OK)
+		return retval;
+
+	return junction_xml_write(pathname, JUNCTION_XATTR_NAME_NFS, doc);
+}
+
+/**
+ * Store NFS locations information into a junction object
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param fslocs list of NFS locations to add
+ * @return a FedFsStatus code
+ *
+ * @note Access to trusted attributes requires CAP_SYS_ADMIN.
+ */
+static FedFsStatus
+nfs_store_locations(const char *pathname, struct nfs_fsloc *fslocs)
+{
+	FedFsStatus retval;
+	xmlDocPtr doc;
+
+	doc = xmlNewDoc((xmlChar *)"1.0");
+	if (doc == NULL) {
+		xlog(D_GENERAL, "%s: Failed to create XML doc for %s",
+			__func__, pathname);
+		return FEDFS_ERR_SVRFAULT;
+	}
+
+	retval = nfs_write_junction(pathname, doc, fslocs);
+
+	xmlFreeDoc(doc);
+	return retval;
+}
+
+/**
+ * Add NFS junction information to a pre-existing object
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param fslocs list of NFS locations to add
+ * @return a FedFsStatus code
+ *
+ * An error occurs if the object referred to by "pathname" does not
+ * exist or contains existing junction data.
+ */
+FedFsStatus
+nfs_add_junction(const char *pathname, struct nfs_fsloc *fslocs)
+{
+	FedFsStatus retval;
+
+	if (fslocs == NULL)
+		return FEDFS_ERR_INVAL;
+
+	retval = nfs_is_prejunction(pathname);
+	if (retval != FEDFS_ERR_NOTJUNCT)
+		return retval;
+
+	retval = nfs_store_locations(pathname, fslocs);
+	if (retval != FEDFS_OK)
+		goto out_err;
+
+	retval = junction_save_mode(pathname);
+	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:
+	(void)nfs_remove_locations(pathname);
+	return retval;
+}
+
+/**
+ * Remove NFS junction information from an object
+ *
+ * @param pathname NUL-terminated C string containing pathname of a directory
+ * @return a FedFsStatus code
+ *
+ * An error occurs if the object referred to by "pathname" does not
+ * exist or does not contain NFS junction data.
+ */
+FedFsStatus
+nfs_delete_junction(const char *pathname)
+{
+	FedFsStatus retval;
+
+	retval = nfs_is_junction(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;
+
+	return nfs_remove_locations(pathname);
+}
+
+/**
+ * Parse the first "host" child of "location"
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param location XML parse tree containing fileset location element
+ * @param fsloc a blank nfs_fsloc to fill in
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_parse_location_host(const char *pathname, xmlNodePtr location,
+		struct nfs_fsloc *fsloc)
+{
+	FedFsStatus retval;
+	xmlChar *hostname;
+	xmlNodePtr node;
+	int hostport;
+
+	retval = FEDFS_ERR_NOTJUNCT;
+	node = junction_xml_find_child_by_name(location, NFS_XML_HOST_TAG);
+	if (node == NULL)
+		return retval;
+
+	hostname = xmlGetProp(node, NFS_XML_HOST_NAME_ATTR);
+	if (!junction_xml_get_int_attribute(node, NFS_XML_HOST_PORT_ATTR,
+							&hostport))
+		fsloc->nfl_hostport = NFS_PORT;
+	else {
+		if (hostport < 1 || hostport > UINT16_MAX) {
+			xlog(D_GENERAL, "%s: Bad port attribute on %s",
+				__func__, pathname);
+			goto out;
+		}
+		fsloc->nfl_hostport = hostport;
+	}
+	if (hostname == NULL) {
+		xlog(D_GENERAL, "%s: No hostname attribute on %s",
+			__func__, pathname);
+		goto out;
+	}
+	fsloc->nfl_hostname = strdup((const char *)hostname);
+	if (fsloc->nfl_hostname == NULL) {
+		retval = FEDFS_ERR_SVRFAULT;
+		goto out;
+	}
+
+	retval = FEDFS_OK;
+
+out:
+	xmlFree(hostname);
+	return retval;
+}
+
+/**
+ * Parse the first "path" child of "location" into a path array
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param location XML parse tree containing fileset location element
+ * @param fsloc a blank nfs_fsloc to fill in
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_parse_location_path(const char *pathname, xmlNodePtr location,
+		struct nfs_fsloc *fsloc)
+{
+	xmlNodePtr node, component;
+	unsigned int count;
+	xmlChar *value;
+	char **result;
+
+	node = junction_xml_find_child_by_name(location, NFS_XML_PATH_TAG);
+	if (node == NULL)
+		return FEDFS_ERR_NOTJUNCT;
+
+	count = 0;
+	for (component = node->children;
+	     component != NULL;
+	     component = component->next) {
+		if (!junction_xml_match_node_name(component,
+						NFS_XML_COMPONENT_TAG))
+			continue;
+		value = xmlNodeGetContent(component);
+		if (junction_xml_is_empty(value)) {
+			xlog(D_GENERAL, "%s: Bad pathname component in %s",
+				__func__, pathname);
+			return FEDFS_ERR_NOTJUNCT;
+		}
+		xmlFree(value);
+		count++;
+	}
+	xlog(D_GENERAL, "%s: Found %u component(s)", __func__, count);
+
+	if (count == 0) {
+		xlog(D_GENERAL, "%s: Zero-component pathname", __func__);
+		fsloc->nfl_rootpath = (char **)calloc(1, sizeof(char *));
+		if (fsloc->nfl_rootpath == NULL)
+			return FEDFS_ERR_SVRFAULT;
+		fsloc->nfl_rootpath[0] = NULL;
+		return FEDFS_OK;
+	}
+
+	result = calloc(count + 1, sizeof(char *));
+	if (result == NULL)
+		return FEDFS_ERR_SVRFAULT;
+
+	count = 0;
+	for (component = node->children;
+	     component != NULL;
+	     component = component->next) {
+		if (!junction_xml_match_node_name(component,
+						NFS_XML_COMPONENT_TAG))
+			continue;
+		value = xmlNodeGetContent(component);
+		result[count] = strdup((const char *)value);
+		xmlFree(value);
+		if (result[count] == NULL) {
+			nfs_free_string_array(result);
+			return FEDFS_ERR_SVRFAULT;
+		}
+		count++;
+	}
+
+	fsloc->nfl_rootpath = result;
+	return FEDFS_OK;
+}
+
+/**
+ * Parse the first "version" child of "location"
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param location XML parse tree containing fileset location element
+ * @param fsloc a blank nfs_fsloc to fill in
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_parse_location_version(const char *pathname, xmlNodePtr location,
+		struct nfs_fsloc *fsloc)
+{
+	xmlNodePtr node;
+
+	node = junction_xml_find_child_by_name(location, NFS_XML_VERSION_TAG);
+	if (node == NULL)
+		goto out_err;
+
+	if (!junction_xml_get_int_attribute(node, NFS_XML_VERSION_MAJOR_ATTR,
+							&fsloc->nfl_majorver))
+		goto out_err;
+	if (!junction_xml_get_int_attribute(node, NFS_XML_VERSION_MINOR_ATTR,
+							&fsloc->nfl_minorver))
+		goto out_err;
+
+	return FEDFS_OK;
+
+out_err:
+	xlog(D_GENERAL, "%s: Missing or invalid version element in %s",
+		__func__, pathname);
+	return FEDFS_ERR_NOTJUNCT;
+}
+
+/**
+ * Parse the first "currency" child of "location"
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param location XML parse tree containing fileset location element
+ * @param fsloc a blank nfs_fsloc to fill in
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_parse_location_currency(const char *pathname, xmlNodePtr location,
+		struct nfs_fsloc *fsloc)
+{
+	xmlNodePtr node;
+
+	node = junction_xml_find_child_by_name(location, NFS_XML_CURRENCY_TAG);
+	if (node == NULL)
+		goto out_err;
+
+	if (!junction_xml_get_int_content(node, &fsloc->nfl_currency))
+		goto out_err;
+
+	return FEDFS_OK;
+
+out_err:
+	xlog(D_GENERAL, "%s: Missing or invalid currency element in %s",
+		__func__, pathname);
+	return FEDFS_ERR_NOTJUNCT;
+}
+
+/**
+ * Parse the first "genflags" child of "location"
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param location XML parse tree containing fileset location element
+ * @param fsloc a blank nfs_fsloc to fill in
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_parse_location_genflags(const char *pathname, xmlNodePtr location,
+		struct nfs_fsloc *fsloc)
+{
+	xmlNodePtr node;
+
+	node = junction_xml_find_child_by_name(location, NFS_XML_GENFLAGS_TAG);
+	if (node == NULL)
+		goto out_err;
+
+	if (!junction_xml_get_bool_attribute(node,
+					NFS_XML_GENFLAGS_WRITABLE_ATTR,
+					&fsloc->nfl_genflags.nfl_writable))
+		goto out_err;
+	if (!junction_xml_get_bool_attribute(node,
+					NFS_XML_GENFLAGS_GOING_ATTR,
+					&fsloc->nfl_genflags.nfl_going))
+		goto out_err;
+	if (!junction_xml_get_bool_attribute(node,
+					NFS_XML_GENFLAGS_SPLIT_ATTR,
+					&fsloc->nfl_genflags.nfl_split))
+		goto out_err;
+
+	return FEDFS_OK;
+
+out_err:
+	xlog(D_GENERAL, "%s: Missing or invalid genflags element in %s",
+		__func__, pathname);
+	return FEDFS_ERR_NOTJUNCT;
+}
+
+/**
+ * Parse the first "transflags" child of "location"
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param location XML parse tree containing fileset location element
+ * @param fsloc a blank nfs_fsloc to fill in
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_parse_location_transflags(const char *pathname, xmlNodePtr location,
+		struct nfs_fsloc *fsloc)
+{
+	xmlNodePtr node;
+
+	node = junction_xml_find_child_by_name(location, NFS_XML_TRANSFLAGS_TAG);
+	if (node == NULL)
+		goto out_err;
+
+	if (!junction_xml_get_bool_attribute(node,
+					NFS_XML_TRANSFLAGS_RDMA_ATTR,
+					&fsloc->nfl_transflags.nfl_rdma))
+		goto out_err;
+
+	return FEDFS_OK;
+
+out_err:
+	xlog(D_GENERAL, "%s: Missing or invalid transflags element in %s",
+		__func__, pathname);
+	return FEDFS_ERR_NOTJUNCT;
+}
+
+/**
+ * Parse the first "class" child of "location"
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param location XML parse tree containing fileset location element
+ * @param fsloc a blank nfs_fsloc to fill in
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_parse_location_class(const char *pathname, xmlNodePtr location,
+		struct nfs_fsloc *fsloc)
+{
+	xmlNodePtr node;
+
+	node = junction_xml_find_child_by_name(location, NFS_XML_CLASS_TAG);
+	if (node == NULL)
+		goto out_err;
+
+	if (!junction_xml_get_u8_attribute(node,
+					NFS_XML_CLASS_SIMUL_ATTR,
+					&fsloc->nfl_info.nfl_simul))
+		goto out_err;
+	if (!junction_xml_get_u8_attribute(node,
+					NFS_XML_CLASS_HANDLE_ATTR,
+					&fsloc->nfl_info.nfl_handle))
+		goto out_err;
+	if (!junction_xml_get_u8_attribute(node,
+					NFS_XML_CLASS_FILEID_ATTR,
+					&fsloc->nfl_info.nfl_fileid))
+		goto out_err;
+	if (!junction_xml_get_u8_attribute(node,
+					NFS_XML_CLASS_WRITEVER_ATTR,
+					&fsloc->nfl_info.nfl_writever))
+		goto out_err;
+	if (!junction_xml_get_u8_attribute(node,
+					NFS_XML_CLASS_WRITEVER_ATTR,
+					&fsloc->nfl_info.nfl_writever))
+		goto out_err;
+	if (!junction_xml_get_u8_attribute(node,
+					NFS_XML_CLASS_CHANGE_ATTR,
+					&fsloc->nfl_info.nfl_change))
+		goto out_err;
+	if (!junction_xml_get_u8_attribute(node,
+					NFS_XML_CLASS_READDIR_ATTR,
+					&fsloc->nfl_info.nfl_readdir))
+		goto out_err;
+
+	return FEDFS_OK;
+
+out_err:
+	xlog(D_GENERAL, "%s: Missing or invalid class element in %s",
+		__func__, pathname);
+	return FEDFS_ERR_NOTJUNCT;
+}
+
+/**
+ * Parse the first "read" child of "location"
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param location XML parse tree containing fileset location element
+ * @param fsloc a blank nfs_fsloc to fill in
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_parse_location_read(const char *pathname, xmlNodePtr location,
+		struct nfs_fsloc *fsloc)
+{
+	xmlNodePtr node;
+
+	node = junction_xml_find_child_by_name(location, NFS_XML_READ_TAG);
+	if (node == NULL)
+		goto out_err;
+
+	if (!junction_xml_get_u8_attribute(node,
+					NFS_XML_READ_RANK_ATTR,
+					&fsloc->nfl_info.nfl_readrank))
+		goto out_err;
+	if (!junction_xml_get_u8_attribute(node,
+					NFS_XML_READ_ORDER_ATTR,
+					&fsloc->nfl_info.nfl_readorder))
+		goto out_err;
+
+	return FEDFS_OK;
+
+out_err:
+	xlog(D_GENERAL, "%s: Missing or invalid read element in %s",
+		__func__, pathname);
+	return FEDFS_ERR_NOTJUNCT;
+}
+
+/**
+ * Parse the first "write" child of "location"
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param location XML parse tree containing fileset location element
+ * @param fsloc a blank nfs_fsloc to fill in
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_parse_location_write(const char *pathname, xmlNodePtr location,
+		struct nfs_fsloc *fsloc)
+{
+	xmlNodePtr node;
+
+	node = junction_xml_find_child_by_name(location, NFS_XML_WRITE_TAG);
+	if (node == NULL)
+		goto out_err;
+
+	if (!junction_xml_get_u8_attribute(node,
+					NFS_XML_WRITE_RANK_ATTR,
+					&fsloc->nfl_info.nfl_writerank))
+		goto out_err;
+	if (!junction_xml_get_u8_attribute(node,
+					NFS_XML_WRITE_ORDER_ATTR,
+					&fsloc->nfl_info.nfl_writeorder))
+		goto out_err;
+
+	return FEDFS_OK;
+
+out_err:
+	xlog(D_GENERAL, "%s: Missing or invalid write element in %s",
+		__func__, pathname);
+	return FEDFS_ERR_NOTJUNCT;
+}
+
+/**
+ * Parse the first "flags" child of "location"
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param location XML parse tree containing fileset location element
+ * @param fsloc a blank nfs_fsloc to fill in
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_parse_location_flags(const char *pathname, xmlNodePtr location,
+		struct nfs_fsloc *fsloc)
+{
+	xmlNodePtr node;
+
+	node = junction_xml_find_child_by_name(location, NFS_XML_FLAGS_TAG);
+	if (node == NULL)
+		goto out_err;
+
+	if (!junction_xml_get_bool_attribute(node,
+					NFS_XML_FLAGS_VARSUB_ATTR,
+					&fsloc->nfl_flags.nfl_varsub))
+		goto out_err;
+
+	return FEDFS_OK;
+
+out_err:
+	xlog(D_GENERAL, "%s: Missing or invalid flags element in %s",
+		__func__, pathname);
+	return FEDFS_ERR_NOTJUNCT;
+}
+
+/**
+ * Parse the first "validfor" child of "location"
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param location XML parse tree containing fileset location element
+ * @param fsloc a blank nfs_fsloc to fill in
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_parse_location_validfor(const char *pathname, xmlNodePtr location,
+		struct nfs_fsloc *fsloc)
+{
+	xmlNodePtr node;
+
+	node = junction_xml_find_child_by_name(location, NFS_XML_VALIDFOR_TAG);
+	if (node == NULL)
+		goto out_err;
+
+	if (!junction_xml_get_int_content(node, &fsloc->nfl_validfor))
+		goto out_err;
+
+	return FEDFS_OK;
+
+out_err:
+	xlog(D_GENERAL, "%s: Missing or invalid validfor element in %s",
+		__func__, pathname);
+	return FEDFS_ERR_NOTJUNCT;
+}
+
+/**
+ * Parse the first "ttl" child of "location"
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param location XML parse tree containing fileset location element
+ * @param fsloc a blank nfs_fsloc to fill in
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfs_parse_location_ttl(const char *pathname, xmlNodePtr location,
+		struct nfs_fsloc *fsloc)
+{
+	xmlNodePtr node;
+
+	node = junction_xml_find_child_by_name(location, NFS_XML_TTL_TAG);
+	if (node == NULL)
+		goto out_err;
+
+	if (!junction_xml_get_int_content(node, &fsloc->nfl_ttl))
+		goto out_err;
+
+	return FEDFS_OK;
+
+out_err:
+	xlog(D_GENERAL, "%s: Missing or invalid ttl element in %s",
+		__func__, pathname);
+	return FEDFS_ERR_NOTJUNCT;
+}
+
+/**
+ * Parse children of NFS location element in an NFS junction
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param location XML parse tree containing fileset location element
+ * @param fsloc a blank nfs_fsloc to fill in
+ * @return a FedFsStatus code
+ *
+ * All children are required only-once elements, and may appear in any order.
+ * Extraneous or repeated elements are ignored for now.
+ */
+static FedFsStatus
+nfs_parse_location_children(const char *pathname, xmlNodePtr location,
+		struct nfs_fsloc *fsloc)
+{
+	FedFsStatus retval;
+
+	retval = nfs_parse_location_host(pathname, location, fsloc);
+	if (retval != FEDFS_OK)
+		return retval;
+	retval = nfs_parse_location_path(pathname, location, fsloc);
+	if (retval != FEDFS_OK)
+		return retval;
+	retval = nfs_parse_location_version(pathname, location, fsloc);
+	if (retval != FEDFS_OK)
+		return retval;
+	retval = nfs_parse_location_currency(pathname, location, fsloc);
+	if (retval != FEDFS_OK)
+		return retval;
+	retval = nfs_parse_location_genflags(pathname, location, fsloc);
+	if (retval != FEDFS_OK)
+		return retval;
+	retval = nfs_parse_location_transflags(pathname, location, fsloc);
+	if (retval != FEDFS_OK)
+		return retval;
+	retval = nfs_parse_location_class(pathname, location, fsloc);
+	if (retval != FEDFS_OK)
+		return retval;
+	retval = nfs_parse_location_read(pathname, location, fsloc);
+	if (retval != FEDFS_OK)
+		return retval;
+	retval = nfs_parse_location_write(pathname, location, fsloc);
+	if (retval != FEDFS_OK)
+		return retval;
+	retval = nfs_parse_location_flags(pathname, location, fsloc);
+	if (retval != FEDFS_OK)
+		return retval;
+	retval = nfs_parse_location_validfor(pathname, location, fsloc);
+	if (retval != FEDFS_OK)
+		return retval;
+	return nfs_parse_location_ttl(pathname, location, fsloc);
+}
+
+/**
+ * Parse NFS location element in an NFS junction
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param location XML parse tree containing fileset location element
+ * @param fsloc OUT: a single NFS location item
+ * @return a FedFsStatus code
+ *
+ * If nfs_parse_location() returns FEDFS_OK, caller must free the returned
+ * location with nfs_free_location().
+ */
+static FedFsStatus
+nfs_parse_node(const char *pathname, xmlNodePtr location,
+		struct nfs_fsloc **fsloc)
+{
+	struct nfs_fsloc *tmp;
+	FedFsStatus retval;
+
+	tmp = nfs_new_location();
+	if (tmp == NULL)
+		return FEDFS_ERR_SVRFAULT;
+
+	retval = nfs_parse_location_children(pathname, location, tmp);
+	if (retval != FEDFS_OK)
+		nfs_free_location(tmp);
+	else
+		*fsloc = tmp;
+	return retval;
+}
+
+/**
+ * Build list of NFS locations from a nodeset
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param nodeset XML nodeset containing "location" elements
+ * @param fslocs OUT: pointer to a list of NFS locations
+ * @return a FedFsStatus code
+ *
+ * If nfs_parse_nodeset() returns FEDFS_OK, caller must free the returned
+ * list of locations with nfs_free_locations().
+ */
+static FedFsStatus
+nfs_parse_nodeset(const char *pathname, xmlNodeSetPtr nodeset,
+		struct nfs_fsloc **fslocs)
+{
+	struct nfs_fsloc *location, *result = NULL;
+	FedFsStatus retval;
+	int i;
+
+	if (xmlXPathNodeSetIsEmpty(nodeset)) {
+		xlog(D_GENERAL, "%s: No fileset locations found in %s",
+			__func__, pathname);
+		return FEDFS_ERR_NOTJUNCT;
+	}
+
+	for (i = 0; i < nodeset->nodeNr; i++) {
+		xmlNodePtr node = nodeset->nodeTab[i];
+
+		retval = nfs_parse_node(pathname, node, &location);
+		if (retval != FEDFS_OK) {
+			nfs_free_locations(result);
+			return retval;
+		}
+
+		if (result == NULL)
+			result = location;
+		else
+			result->nfl_next = location;
+	}
+
+	*fslocs = result;
+	return FEDFS_OK;
+}
+
+/**
+ * Parse fileset location information from junction XML
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param context XML path context containing junction XML
+ * @param fslocs OUT: pointer to a list of NFS locations
+ * @return a FedFsStatus code
+ *
+ * If nfs_parse_context() returns FEDFS_OK, caller must free the returned
+ * list of locations with nfs_free_locations().
+ */
+static FedFsStatus
+nfs_parse_context(const char *pathname, xmlXPathContextPtr context,
+		struct nfs_fsloc **fslocs)
+{
+	xmlXPathObjectPtr object;
+	FedFsStatus retval;
+
+	object = xmlXPathEvalExpression(NFS_XML_LOCATION_XPATH, context);
+	if (object == NULL) {
+		xlog(D_GENERAL, "%s: Failed to evaluate XML in %s",
+			__func__, pathname);
+		return FEDFS_ERR_NOTJUNCT;
+	}
+
+	retval = nfs_parse_nodeset(pathname, object->nodesetval, fslocs);
+
+	xmlXPathFreeObject(object);
+	return retval;
+}
+
+/**
+ * Parse NFS locations 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 fslocs OUT: pointer to a list of NFS locations
+ * @return a FedFsStatus code
+ *
+ * If nfs_parse_xml() returns FEDFS_OK, caller must free the returned
+ * list of locations with nfs_free_locations().
+ */
+static FedFsStatus
+nfs_parse_xml(const char *pathname, xmlDocPtr doc, struct nfs_fsloc **fslocs)
+{
+	xmlXPathContextPtr context;
+	FedFsStatus retval;
+
+	context = xmlXPathNewContext(doc);
+	if (context == NULL) {
+		xlog(D_GENERAL, "%s: Failed to create XPath context from %s",
+			__func__, pathname);
+		return FEDFS_ERR_SVRFAULT;
+	}
+
+	retval = nfs_parse_context(pathname, context, fslocs);
+
+	xmlXPathFreeContext(context);
+	return retval;
+}
+
+/**
+ * Retrieve list of NFS locations from an NFS junction
+ *
+ * @param pathname NUL-terminated C string containing pathname of a junction
+ * @param fslocs OUT: pointer to a list of NFS locations
+ * @return a FedFsStatus code
+ *
+ * If nfs_get_locations() returns FEDFS_OK, caller must free the returned
+ * list of locations with nfs_free_locations().
+ */
+FedFsStatus
+nfs_get_locations(const char *pathname, struct nfs_fsloc **fslocs)
+{
+	FedFsStatus retval;
+	xmlDocPtr doc;
+
+	if (fslocs == NULL)
+		return FEDFS_ERR_INVAL;
+
+	retval = junction_xml_parse(pathname, JUNCTION_XATTR_NAME_NFS, &doc);
+	if (retval != FEDFS_OK)
+		return retval;
+
+	retval = nfs_parse_xml(pathname, doc, fslocs);
+
+	xmlFreeDoc(doc);
+	return retval;
+}
+
+/**
+ * Predicate: does "pathname" refer to an object that can become an NFS junction?
+ *
+ * @param pathname NUL-terminated C string containing pathname of a directory
+ * @return a FedFsStatus code
+ *
+ * Return values:
+ *	FEDFS_ERR_NOTJUNCT:	"pathname" refers to an object that can be
+ *				made into a NFS junction
+ *	FEDFS_ERR_EXIST:	"pathname" refers to something that is
+ *				already a junction
+ *	FEDFS_ERR_INVAL:	"pathname" does not exist
+ *	Other:			Some error occurred, "pathname" not
+ *				investigated
+ */
+FedFsStatus
+nfs_is_prejunction(const char *pathname)
+{
+	FedFsStatus retval;
+	int fd;
+
+	retval = junction_open_path(pathname, &fd);
+	if (retval != FEDFS_OK)
+		return retval;
+
+	retval = junction_is_directory(fd, pathname);
+	if (retval != FEDFS_OK)
+		goto out_close;
+
+	retval = junction_is_sticky_bit_set(fd, pathname);
+	switch (retval) {
+	case FEDFS_ERR_NOTJUNCT:
+		break;
+	case FEDFS_OK:
+		goto out_exist;
+	default:
+		goto out_close;
+	}
+
+	retval = junction_is_xattr_present(fd, pathname, JUNCTION_XATTR_NAME_NFS);
+	switch (retval) {
+	case FEDFS_ERR_NOTJUNCT:
+		break;
+	case FEDFS_OK:
+		goto out_exist;
+	default:
+		goto out_close;
+	}
+
+out_close:
+	(void)close(fd);
+	return retval;
+out_exist:
+	retval = FEDFS_ERR_EXIST;
+	goto out_close;
+}
+
+/**
+ * Verify that junction contains NFS junction XML
+ *
+ * @param pathname NUL-terminated C string containing pathname of a directory
+ * @return a FedFsStatus code
+ *
+ * Return values:
+ *	FEDFS_OK:		"pathname" refers to an NFS junction
+ *	FEDFS_ERR_NOTJUNCT:	"pathname" refers to something that is
+ *				not an NFS 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
+nfs_is_junction_xml(const char *pathname)
+{
+	struct nfs_fsloc *fslocs = NULL;
+	FedFsStatus retval;
+	xmlDocPtr doc;
+
+	retval = junction_xml_parse(pathname, JUNCTION_XATTR_NAME_NFS, &doc);
+	if (retval != FEDFS_OK)
+		return retval;
+
+	retval = nfs_parse_xml(pathname, doc, &fslocs);
+	nfs_free_locations(fslocs);
+
+	xmlFreeDoc(doc);
+	return retval;
+}
+
+/**
+ * Predicate: does "pathname" refer to an NFS junction?
+ *
+ * @param pathname NUL-terminated C string containing pathname of a directory
+ * @return a FedFsStatus code
+ *
+ * Return values:
+ *	FEDFS_OK:		"pathname" refers to an NFS junction
+ *	FEDFS_ERR_NOTJUNCT:	"pathname" refers to an object that is
+ *				not a junction
+ *	FEDFS_ERR_INVAL:	"pathname" does not exist
+ *	Other:			Some error occurred, "pathname" not
+ *				investigated
+ */
+FedFsStatus
+nfs_is_junction(const char *pathname)
+{
+	FedFsStatus retval;
+	int fd;
+
+	retval = junction_open_path(pathname, &fd);
+	if (retval != FEDFS_OK)
+		return retval;
+
+	retval = junction_is_directory(fd, pathname);
+	if (retval != FEDFS_OK)
+		goto out_close;
+
+	retval = junction_is_sticky_bit_set(fd, pathname);
+	if (retval != FEDFS_OK)
+		goto out_close;
+
+	retval = junction_is_xattr_present(fd, pathname, JUNCTION_XATTR_NAME_NFS);
+	if (retval != FEDFS_OK)
+		goto out_close;
+
+	(void)close(fd);
+
+	return nfs_is_junction_xml(pathname);
+
+out_close:
+	(void)close(fd);
+	return retval;
+}
diff --git a/src/libjunction/xml.c b/src/libjunction/xml.c
index bc77fdc..e6f9e89 100644
--- a/src/libjunction/xml.c
+++ b/src/libjunction/xml.c
@@ -65,6 +65,110 @@  junction_xml_match_node_name(xmlNodePtr node, const xmlChar *name)
 }
 
 /**
+ * Find a first-level child of "parent" named "name"
+ *
+ * @param parent pointer to node whose children are to be searched
+ * @param name NUL-terminated C string containing name to match
+ * @return pointer to child of "parent" whose name is "name"
+ */
+xmlNodePtr
+junction_xml_find_child_by_name(xmlNodePtr parent, const xmlChar *name)
+{
+	xmlNodePtr node;
+
+	for (node = parent->children; node != NULL; node = node->next)
+		if (junction_xml_match_node_name(node, name))
+			return node;
+	return NULL;
+}
+
+/**
+ * Read attribute into a boolean
+ *
+ * @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 boolean value
+ */
+_Bool
+junction_xml_get_bool_attribute(xmlNodePtr node, const xmlChar *attrname,
+		_Bool *value)
+{
+	xmlChar *prop;
+	_Bool retval;
+
+	retval = false;
+	prop = xmlGetProp(node, attrname);
+	if (prop == NULL)
+		goto out;
+
+	if (xmlStrcmp(prop, (const xmlChar *)"true") == 0) {
+		*value = true;
+		retval = true;
+		goto out;
+	}
+
+	if (xmlStrcmp(prop, (const xmlChar *)"false") == 0) {
+		*value = false;
+		retval = true;
+		goto out;
+	}
+
+out:
+	xmlFree(prop);
+	return retval;
+}
+
+/**
+ * Set attribute to a boolean
+ *
+ * @param node pointer to a node in an XML parse tree
+ * @param attrname NUL-terminated C string containing attribute name
+ * @param value boolean value to set
+ */
+void
+junction_xml_set_bool_attribute(xmlNodePtr node, const xmlChar *attrname,
+					_Bool value)
+{
+	xmlSetProp(node, attrname, (const xmlChar *)(value ? "true" : "false"));
+}
+
+/**
+ * Read attribute into an uint8_t
+ *
+ * @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 uint8_t
+ * @return true if attribute "attrname" has a valid uint8_t value
+ */
+_Bool
+junction_xml_get_u8_attribute(xmlNodePtr node, const xmlChar *attrname,
+		uint8_t *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 > 255 || tmp < 0)
+		goto out;
+
+	*value = (uint8_t)tmp;
+	retval = true;
+
+out:
+	xmlFree(prop);
+	return retval;
+}
+
+/**
  * Read attribute into an integer
  *
  * @param node pointer to a node in an XML parse tree
@@ -105,7 +209,6 @@  out:
  * @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,
@@ -118,6 +221,56 @@  junction_xml_set_int_attribute(xmlNodePtr node, const xmlChar *attrname,
 }
 
 /**
+ * Read node content into an integer
+ *
+ * @param node pointer to a node in an XML parse tree
+ * @param value OUT: node's content converted to an integer 
+ * @return true if "node" has valid integer content
+ */
+_Bool
+junction_xml_get_int_content(xmlNodePtr node, int *value)
+{
+	xmlChar *content;
+	char *endptr;
+	_Bool retval;
+	long tmp;
+
+	retval = false;
+	content = xmlNodeGetContent(node);
+	if (content == NULL)
+		goto out;
+
+	errno = 0;
+	tmp = strtol((const char *)content, &endptr, 10);
+	if (errno != 0 || *endptr != '\0' || tmp > INT32_MAX || tmp < INT32_MIN)
+		goto out;
+
+	*value = (int)tmp;
+	retval = true;
+
+out:
+	xmlFree(content);
+	return retval;
+}
+
+/**
+ * Add a child node with integer content
+ *
+ * @param parent  pointer to a node in an XML parse tree
+ * @param name NUL-terminated C string containing name of child to add
+ * @param value set node content to this value
+ * @return pointer to new child node
+ */
+xmlNodePtr
+junction_xml_set_int_content(xmlNodePtr parent, const xmlChar *name, int value)
+{
+	char buf[16];
+
+	snprintf(buf, sizeof(buf), "%d", value);
+	return xmlNewTextChild(parent, NULL, name, (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