Patchwork [1/3] nfsref: Introduce the "nfsref" command

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

Comments

Chuck Lever - Jan. 12, 2012, 4:33 p.m.
Introduce an administrative command line interface for creating local
junctions.  This is modeled after the Solaris utility of the same
name.

The interface provides simple junction management functionality, but
does not expose a complete set of junction administrative tools.  For
example, creating a FedFS junction always creates a fresh FSN and FSL
records.  To share these records among multiple junctions requires a
more sophisticated administrative interface.

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

 .gitignore             |    1 
 INSTALL                |    2 
 configure.ac           |    1 
 doc/man/Makefile.am    |    2 
 doc/man/nfsref.8       |  283 ++++++++++++++++++++++
 src/Makefile.am        |    4 
 src/nfsref/Makefile.am |   41 +++
 src/nfsref/add.c       |  620 ++++++++++++++++++++++++++++++++++++++++++++++++
 src/nfsref/lookup.c    |  410 ++++++++++++++++++++++++++++++++
 src/nfsref/nfsref.c    |  187 ++++++++++++++
 src/nfsref/nfsref.h    |   43 +++
 src/nfsref/remove.c    |  163 +++++++++++++
 12 files changed, 1754 insertions(+), 3 deletions(-)
 create mode 100644 doc/man/nfsref.8
 create mode 100644 src/nfsref/Makefile.am
 create mode 100644 src/nfsref/add.c
 create mode 100644 src/nfsref/lookup.c
 create mode 100644 src/nfsref/nfsref.c
 create mode 100644 src/nfsref/nfsref.h
 create mode 100644 src/nfsref/remove.c

Patch

diff --git a/.gitignore b/.gitignore
index 468a533..10c43a2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@  Doxygen/
 src/fedfsd/fedfsd
 src/resolve-junction/resolve-junction
 src/nsdbparams/nsdbparams
+src/nfsref/nfsref
 src/mount/mount.fedfs
 src/mount/fedfs-map-nfs4
 libadmin.a
diff --git a/INSTALL b/INSTALL
index 417c34a..186678d 100644
--- a/INSTALL
+++ b/INSTALL
@@ -125,6 +125,8 @@  FedFS file server
 
   o Install mountd with patches to resolve junctions
 
+  o Install the nfsref program
+
   o Set up a fedfs user ID and group ID
 
   o Create /var/lib/fedfs and set its user and group to the fedfs
diff --git a/configure.ac b/configure.ac
index a5afb12..bbe3f19 100644
--- a/configure.ac
+++ b/configure.ac
@@ -170,6 +170,7 @@  AC_CONFIG_FILES([Makefile
                  src/libsi/Makefile
                  src/libxlog/Makefile
                  src/mount/Makefile
+                 src/nfsref/Makefile
                  src/nsdbc/Makefile
                  src/nsdbparams/Makefile
                  src/resolve-junction/Makefile])
diff --git a/doc/man/Makefile.am b/doc/man/Makefile.am
index c9ddfe8..30016ce 100644
--- a/doc/man/Makefile.am
+++ b/doc/man/Makefile.am
@@ -37,7 +37,7 @@  NSDB_CLIENT_CMDS	= nsdb-create-fsl.8 nsdb-create-fsn.8 \
 			  nsdb-delete-nsdb.8
 
 dist_man7_MANS		= fedfs.7
-dist_man8_MANS		= rpc.fedfsd.8 mount.fedfs.8 fedfs-map-nfs4.8 \
+dist_man8_MANS		= rpc.fedfsd.8 mount.fedfs.8 fedfs-map-nfs4.8 nfsref.8 \
 			  nsdbparams.8 $(FEDFS_CLIENT_CMDS) $(NSDB_CLIENT_CMDS)
 
 CLEANFILES		= cscope.in.out cscope.out cscope.po.out *~
diff --git a/doc/man/nfsref.8 b/doc/man/nfsref.8
new file mode 100644
index 0000000..ed70366
--- /dev/null
+++ b/doc/man/nfsref.8
@@ -0,0 +1,283 @@ 
+.\"@(#)nfsref.8"
+.\"
+.\" @file doc/man/nfsref.8
+.\" @brief man page for nfsref command
+.\"
+
+.\"
+.\" 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
+.\"
+.TH NFSREF 8 "@publication-date@"
+.SH NAME
+nfsref \- manage NFS referrals
+.SH SYNOPSIS
+.B nfsref
+.RB [ \-?d ]
+.RB [ \-t
+.IB type ]
+.B add
+.I pathname server export
+.RI [ " server"
+.IR export " ... ]"
+.P
+.B nfsref
+.RB [ \-?d ]
+.RB [ \-t
+.IB type ]
+.B remove
+.I pathname
+.P
+.B nfsref
+.RB [ \-?d ]
+.RB [ \-t
+.IB type ]
+.B lookup
+.I pathname
+.SH INTRODUCTION
+NFS version 4 introduces the concept of
+.I file system referrals
+to NFS.
+A file system referral is like a server-side symbolic link
+to another file system share, possibly on another file server.
+On an NFS client, a referral behaves like an automounted directory.
+The client, under the server's direction, mounts a new NFS export
+automatically when an application first accesses that directory.
+.P
+Referrals are typically used to construct a single file name space
+across multiple file servers.
+Because file servers control the shape of the name space,
+no client configuration is required,
+and all clients see the same referral information.
+.P
+The Linux NFS server supports NFS version 4 referrals.
+Administrators can specify the
+.B refer=
+export option in
+.I /etc/exports
+to configure a list of exports from which the client can choose.
+See
+.BR exports (5)
+for details.
+.P
+The
+.BR nfsref (8)
+command provides an alternate way to configure NFS referrals.
+This command stores referral information
+as metadata within a leaf directory in an exported file system.
+The metadata it stores can contain one of two types of information:
+.IP "\fIA list of Fileset Locations\fP"
+A set of server name and export path pairs which are returned
+verbatim to clients during an NFS referral event.
+This is known as an
+.IR "NFS basic junction" .
+.IP "\fIA Fileset Name\fP"
+The name of an LDAP record which contains information to return
+to clients during an NFS referral event.
+This is known as a
+.IR "FedFS junction" .
+.P
+A directory can hold either an NFS basic junction or a FedFS junction,
+but not both.
+When a directory acts as a junction, its regular contents remain,
+but are no longer visible to NFS clients.
+.P
+By storing the location information in an LDAP directory,
+FedFS junctions on multiple file servers can refer to
+the same copy of location information.
+This common locations metadata can be updated
+via a single administrative operation,
+altering the file name space consistently across all servers.
+The
+.BR fedfs (7)
+man page has more information.
+.SH DESCRIPTION
+The
+.BR nfsref (8)
+command is a simple way to get started managing junction metadata.
+Other administrative commands provide richer access to junction information.
+.SS Subcommands
+Valid
+.BR nfsref (8)
+subcommands are:
+.IP "\fBadd\fP"
+Adds junction information to the directory named by
+.IR pathname .
+The named directory must already exist,
+and must not already contain junction information.
+Regular directory contents are obscured to NFS clients by this operation.
+.IP
+A list of one or more file server and export path pairs
+is also specified on the command line.
+When creating an NFS basic junction, this list is
+stored in an extended attribute of the directory.
+.IP
+When creating a FedFS junction, FedFS records containing the
+file server and export path pairs are created on an LDAP server,
+and a pointer to the new FedFS records is
+stored in an extended attribute of the directory.
+Fresh FSN and FSL UUIDs are generated during this operation.
+.IP
+If junction creation is successful, the
+.BR nfsref (8)
+command flushes the kernel's export cache
+to remove previously cached junction information.
+.IP "\fBremove\fP"
+Removes junction information from the directory named by
+.IR pathname .
+The named directory must exist,
+and must contain junction information.
+Regular directory contents are made visible to NFS clients again by this operation.
+.IP
+When removing a FedFS junction,
+existing FedFS records remain on the LDAP server.
+They can be removed by other means if it is known
+that no other junction refers to these records.
+.IP
+If junction deletion is successful, the
+.BR nfsref (8)
+command flushes the kernel's export cache
+to remove previously cached junction information.
+.IP "\fBlookup\fP"
+Displays junction information stored in the directory named by
+.IR pathname .
+The named directory must exist,
+and must contain junction information.
+.IP
+When looking up an NFS basic junction, the junction information
+in the directory is listed on
+.IR stdout .
+When looking up a FedFS junction, junction information is
+retrieved from the LDAP server listed in the junction
+and listed on
+.IR stdout .
+.P
+When creating a new FedFS junction, the
+.BR nfsref (8)
+command reads the following environment variables:
+.IP "\fBFEDFS_NSDB_HOST\fP"
+Specifies the hostname of the LDAP server where new FedFS records
+should reside.  If this variable is not set, the
+.BR nfsref (8)
+command fails.
+The LDAP server specified by this variable
+must be registered with the local NSDB connection
+parameter database before the
+.BR nfsref (8)
+command can communicate with it.  See
+.BR nsdbparams (8)
+for more information.
+.IP "\fBFEDFS_NSDB_PORT\fP"
+Specifies the IP port of the LDAP server where new FedFS records
+should reside.  The default value if this variable is not set is 389.
+.IP "\fBFEDFS_NSDB_NCE\fP"
+Specifies the distinguished name of the NSDB Container Entry
+under which new FedFS records should reside.
+If this variable is not set, the local NSDB connection parameter
+database is searched for a default NCE for the hostname specified by
+.BR FEDFS_NSDB_HOST .
+If neither of these is specified, the
+.BR nfsref (8)
+command fails.
+.IP "\fBFEDFS_NSDB_ADMIN\fP"
+Specifies a distinguished name of an entity used to bind
+to the LDAP server where new FedFS records should reside.
+If this variable is not set, the local NSDB connection parameter
+database is searched for a default bind DN for the hostname
+specified by
+.BR FEDFS_NSDB_HOST .
+If neither of these is specified, or if this entity does not have
+permission to modify the LDAP server's DIT, the
+.BR nfsref (8)
+command fails.
+.IP "\fBFEDFS_NSDB_PASSWD\fP"
+Specifies the password used for simple authentication
+to the LDAP server where new FedFS records should reside.
+If this variable is not set, the
+.BR nfsref (8)
+command asks for a password on
+.IR stdin .
+Standard password blanking techniques are used to obscure the
+password on the user's terminal.
+.SS Command line options
+.IP "\fB\-d, \-\-debug"
+Enables debugging messages during operation.
+.IP "\fB\-t, \-\-type=\fIjunction-type\fP"
+Specifies the junction type for the operation.  Valid values for
+.I junction-type
+are
+.B nfs-basic
+or
+.BR nfs-fedfs .
+.IP
+For the
+.B add
+subcommand, the default value if this option is not specified is
+.BR nfs-basic .
+For the
+.B remove
+and
+.B lookup
+subcommands, the
+.B \-\-type
+option is not required.  The
+.BR nfsref (8)
+command operates on whatever junction contents are available.
+.SH EXAMPLES
+Suppose you have two file servers,
+.I top.example.net
+and
+.IR home.example.net .
+You want all your clients to mount
+.I top.example.net:/
+and then see the files under
+.I home.example.net:/
+automatically in
+.IR top:/home .
+.P
+On
+.IR top.example.net ,
+you might issue this command as root:
+.RS
+.sp
+# mkdir /home
+.br
+# nfsref --type=nfs-basic add /home home.example.net /
+.br
+Created junction /home.
+.sp
+.RE
+.SH FILES
+.TP
+.I /etc/exports
+NFS server export table
+.SH "SEE ALSO"
+.BR fedfs (7),
+.BR nsdbparams (8),
+.BR exports (5)
+.sp
+RFC 3530 for a description of NFS version 4 referrals
+.sp
+RFC 5716 for FedFS requirements and overview
+.SH COLOPHON
+This page is part of the fedfs-utils package.
+A description of the project and information about reporting bugs
+can be found at
+.IR http://oss.oracle.com/projects/fedfs-utils .
+.SH "AUTHOR"
+Chuck Lever <chuck.lever@oracle.com>
diff --git a/src/Makefile.am b/src/Makefile.am
index f4ba864..fcb84e6 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -25,8 +25,8 @@ 
 
 SUBDIRS			= include libxlog libadmin libnsdb libjunction \
 			  libparser libsi \
-			  fedfsc fedfsd nsdbc nsdbparams resolve-junction \
-			  mount
+			  fedfsc fedfsd mount nfsref nsdbc nsdbparams \
+			  resolve-junction
 
 CLEANFILES		= cscope.in.out cscope.out cscope.po.out *~
 DISTCLEANFILES		= Makefile.in
diff --git a/src/nfsref/Makefile.am b/src/nfsref/Makefile.am
new file mode 100644
index 0000000..284d4b4
--- /dev/null
+++ b/src/nfsref/Makefile.am
@@ -0,0 +1,41 @@ 
+##
+## @file src/nfsref/Makefile.am
+## @brief Process this file with automake to produce src/nfsref/Makefile.in
+##
+
+##
+## 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
+##
+
+noinst_HEADERS		= nfsref.h
+sbin_PROGRAMS		= nfsref
+nfsref_SOURCES		= add.c lookup.c nfsref.c remove.c
+LDADD			= $(LIBLDAP) $(LIBLBER) $(LIBXML2) \
+			  $(LIBSQLITE3) $(LIBIDN) $(LIBUUID) \
+			  $(top_builddir)/src/libnsdb/libnsdb.la \
+			  $(top_builddir)/src/libxlog/libxlog.la \
+			  $(top_builddir)/src/libjunction/libjunction.la
+
+CLEANFILES		= cscope.in.out cscope.out cscope.po.out *~
+DISTCLEANFILES		= Makefile.in
+
+AM_CFLAGS		= -ggdb -fstrict-aliasing -fPIE \
+			  -Wall -Wextra -pedantic -Wformat=2 \
+			  -Wstrict-aliasing=2 -Wp,-D_FORTIFY_SOURCE=2
+AM_CPPFLAGS		= -I. -I$(top_srcdir)/src/include
diff --git a/src/nfsref/add.c b/src/nfsref/add.c
new file mode 100644
index 0000000..9b292f5
--- /dev/null
+++ b/src/nfsref/add.c
@@ -0,0 +1,620 @@ 
+/**
+ * @file src/nfsref/add.c
+ * @brief Add junction metadata to a local file system object
+ */
+
+/*
+ * 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 <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <uuid/uuid.h>
+
+#include "fedfs.h"
+#include "junction.h"
+#include "nsdb.h"
+#include "xlog.h"
+#include "nfsref.h"
+
+/**
+ * Fill in default settings for NFSv4.0 fs_locations4
+ *
+ * @param new NFS location structure to fill in
+ *
+ * See section 5.1.3.2 of the NSDB protocol draft.
+ */
+static void
+nfsref_add_fsloc_defaults(struct nfs_fsloc *new)
+{
+	new->nfl_hostport = 0;
+	new->nfl_flags.nfl_varsub = false;
+	new->nfl_currency = -1;
+	new->nfl_validfor = 0;
+	new->nfl_genflags.nfl_writable = false;
+	new->nfl_genflags.nfl_going = false;
+	new->nfl_genflags.nfl_split = true;
+	new->nfl_transflags.nfl_rdma = true;
+	new->nfl_info.nfl_simul = 0;
+	new->nfl_info.nfl_handle = 0;
+	new->nfl_info.nfl_fileid = 0;
+	new->nfl_info.nfl_writever = 0;
+	new->nfl_info.nfl_change = 0;
+	new->nfl_info.nfl_readdir = 0;
+	new->nfl_info.nfl_readrank = 0;
+	new->nfl_info.nfl_readorder = 0;
+	new->nfl_info.nfl_writerank = 0;
+	new->nfl_info.nfl_writeorder = 0;
+	new->nfl_majorver = 4;
+	new->nfl_minorver = 0;
+	new->nfl_ttl = 300;
+}
+
+/**
+ * Convert a pair of command line arguments to one nfs_fsloc structure
+ *
+ * @param server NUL-terminated C string containing file server hostname
+ * @param rootpath NUL-terminated C string containing POSIX-style export path
+ * @param fsloc OUT: NFS location structure
+ * @return a FedFsStatus code
+ *
+ * If nfsref_add_build_fsloc() returns FEDFS_OK, caller must free the
+ * returned fsloc with nfs_free_location().
+ */
+static FedFsStatus
+nfsref_add_build_fsloc(const char *server, const char *rootpath,
+		struct nfs_fsloc **fsloc)
+{
+	struct nfs_fsloc *new;
+	FedFsStatus retval;
+
+	if (server == NULL || rootpath == NULL)
+		return FEDFS_ERR_INVAL;
+
+	xlog(D_GENERAL, "%s: Building fsloc for %s:%s",
+		__func__, server, rootpath);
+
+	new = nfs_new_location();
+	if (new == NULL) {
+		xlog(D_GENERAL, "%s: No memory", __func__);
+		return FEDFS_ERR_SVRFAULT;
+	}
+
+	new->nfl_hostname = strdup(server);
+	if (new->nfl_hostname == NULL) {
+		nfs_free_location(new);
+		xlog(D_GENERAL, "%s: No memory", __func__);
+		return FEDFS_ERR_SVRFAULT;
+	}
+
+	retval = nsdb_posix_to_path_array(rootpath, &new->nfl_rootpath);
+	if (retval != FEDFS_OK) {
+		free(new->nfl_hostname);
+		nfs_free_location(new);
+		return retval;
+	}
+
+	nfsref_add_fsloc_defaults(new);
+	*fsloc = new;
+	return FEDFS_OK;
+}
+
+/**
+ * Convert array of command line arguments to list of nfs_fsloc structures
+ *
+ * @param argv array of pointers to NUL-terminated C strings contains arguments
+ * @param optind index of "argv" where "add" subcommand arguments start
+ * @param fslocs OUT: list of NFS locations
+ * @return a FedFsStatus code
+ *
+ * If nfsref_add_build_fsloc_list() returns FEDFS_OK, caller must free the
+ * returned list of fslocs with nfs_free_locations().
+ */
+static FedFsStatus
+nfsref_add_build_fsloc_list(char **argv, int optind, struct nfs_fsloc **fslocs)
+{
+	struct nfs_fsloc *fsloc, *result = NULL;
+	FedFsStatus retval;
+	int i;
+
+	for (i = optind + 2; argv[i] != NULL; i += 2) {
+		retval = nfsref_add_build_fsloc(argv[i], argv[i + 1], &fsloc);
+		if (retval != FEDFS_OK) {
+			nfs_free_locations(result);
+			return retval;
+		}
+		if (result == NULL)
+			result = fsloc;
+		else
+			result->nfl_next = fsloc;
+	}
+	if (result == NULL)
+		return FEDFS_ERR_INVAL;
+
+	*fslocs = result;
+	return FEDFS_OK;
+}
+
+/**
+ * Add NFS locations to a junction
+ *
+ * @param junct_path NUL-terminated C string containing pathname of junction
+ * @param argv array of pointers to NUL-terminated C strings contains arguments
+ * @param optind index of "argv" where "add" subcommand arguments start
+ * @return program exit status
+ */
+static int
+nfsref_add_nfs_basic(const char *junct_path, char **argv, int optind)
+{
+	struct nfs_fsloc *fslocs = NULL;
+	FedFsStatus retval;
+
+	xlog(D_GENERAL, "%s: Adding basic junction to %s",
+		__func__, junct_path);
+
+	retval = nfsref_add_build_fsloc_list(argv, optind, &fslocs);
+	switch (retval) {
+	case FEDFS_OK:
+		break;
+	case FEDFS_ERR_INVAL:
+		xlog(L_ERROR, "Missing arguments");
+		return EXIT_FAILURE;
+	case FEDFS_ERR_SVRFAULT:
+		xlog(L_ERROR, "No memory");
+		return EXIT_FAILURE;
+	default:
+		xlog(L_ERROR, "Failed to add NFS location metadata to %s: %s",
+			junct_path, nsdb_display_fedfsstatus(retval));
+		return EXIT_FAILURE;
+	}
+
+	retval = nfs_add_junction(junct_path, fslocs);
+	nfs_free_locations(fslocs);
+	switch (retval) {
+	case FEDFS_OK:
+		break;
+	case FEDFS_ERR_EXIST:
+		xlog(L_ERROR, "%s already contains junction metadata",
+			junct_path);
+		return EXIT_FAILURE;
+	default:
+		xlog(L_ERROR, "Failed to add NFS location metadata to %s: %s",
+			junct_path, nsdb_display_fedfsstatus(retval));
+		return EXIT_FAILURE;
+	}
+
+	printf("Created junction %s\n", junct_path);
+	return EXIT_SUCCESS;
+}
+
+/**
+ * Create a FedFS FSN record, return the new UUID
+ *
+ * @param host initialized NSDB host object
+ * @param nce NUL-terminated C string containing DN of NSDB container entry
+ * @param fsn_uuid OUT: freshly generated FSN UUID
+ * @return a FedFsStatus code
+ *
+ * If nfsref_add_create_fedfs_fsn() returns FEDFS_OK, caller must free
+ * the returned FSN UUID with free(3).
+ */
+static FedFsStatus
+nfsref_add_create_fedfs_fsn(nsdb_t host, const char *nce, char **fsn_uuid)
+{
+	unsigned int ldap_err;
+	FedFsStatus retval;
+	char *fsnuuid;
+	uuid_t uu;
+
+	fsnuuid = calloc(FEDFS_UUID_STRLEN, sizeof(char));
+	if (fsnuuid == NULL) {
+		xlog(D_GENERAL, "%s: No memory", __func__);
+		return FEDFS_ERR_SVRFAULT;
+	}
+	uuid_generate_time(uu);
+	uuid_unparse(uu, fsnuuid);
+
+	retval = nsdb_create_fsn_s(host, nce, fsnuuid,
+					nsdb_hostname(host), nsdb_port(host),
+					&ldap_err);
+	switch (retval) {
+	case FEDFS_OK:
+		xlog(D_GENERAL, "%s: Successfully created FSN record "
+			"for %s under %s", __func__, fsnuuid, nce);
+		*fsn_uuid = fsnuuid;
+		return FEDFS_OK;
+	case FEDFS_ERR_NSDB_NONCE:
+		xlog(L_ERROR, "NCE %s does not exist", nce);
+		break;
+	case FEDFS_ERR_NSDB_LDAP_VAL:
+		xlog(L_ERROR, "Failed to create FSN: %s",
+			ldap_err2string(ldap_err));
+		break;
+	default:
+		xlog(L_ERROR, "Failed to create FSN: %s",
+			nsdb_display_fedfsstatus(retval));
+	}
+
+	free(fsnuuid);
+	return retval;
+}
+
+/**
+ * Fill in default settings for NFSv4.0 fs_locations4
+ *
+ * @param rootpath NUL-terminated C string containing POSIX-style export path
+ * @param new fedfs_fsl object to fill in
+ *
+ * See section 5.1.3.2 of the NSDB protocol draft.
+ */
+static FedFsStatus
+nfsref_add_nfs_fsl_defaults(const char *rootpath, struct fedfs_nfs_fsl *new)
+{
+	FedFsStatus retval;
+
+	retval = nsdb_posix_to_path_array(rootpath, &new->fn_nfspath);
+	if (retval != FEDFS_OK)
+		return retval;
+
+	new->fn_majorver = 4;
+	new->fn_minorver = 0;
+	new->fn_currency = -1;
+	new->fn_gen_writable = false;
+	new->fn_gen_going = false;
+	new->fn_gen_split = true;
+	new->fn_trans_rdma = true;
+	new->fn_class_simul = 0;
+	new->fn_class_handle = 0;
+	new->fn_class_fileid = 0;
+	new->fn_class_writever = 0;
+	new->fn_class_change = 0;
+	new->fn_class_readdir = 0;
+	new->fn_readrank = 0;
+	new->fn_readorder = 0;
+	new->fn_writerank = 0;
+	new->fn_writeorder = 0;
+	new->fn_varsub = false;
+	new->fn_validfor = 0;
+
+	return FEDFS_OK;
+}
+
+/**
+ * Convert a pair of command line arguments to one fedfs_fsl structure
+ *
+ * @param fsn_uuid NUL-terminated C string containing FSN UUID to use
+ * @param host an initialized NSDB host object
+ * @param server NUL-terminated C string containing file server hostname
+ * @param rootpath NUL-terminated C string containing POSIX-style export path
+ * @param fsl OUT: fedfs_fsl object
+ * @return a FedFsStatus code
+ *
+ * If nfsref_add_build_fsl() returns FEDFS_OK, caller must free the
+ * returned fsl with nsdb_free_fedfs_fsl().
+ */
+static FedFsStatus
+nfsref_add_build_fsl(const char *fsn_uuid, nsdb_t host, const char *server,
+		const char *rootpath, struct fedfs_fsl **fsl)
+{
+	struct fedfs_fsl *new;
+	FedFsStatus retval;
+	uuid_t uu;
+
+	if (server == NULL || rootpath == NULL)
+		return FEDFS_ERR_INVAL;
+
+	new = nsdb_new_fedfs_fsl(FEDFS_NFS_FSL);
+	if (new == NULL) {
+		xlog(D_GENERAL, "%s: No memory", __func__);
+		return FEDFS_ERR_SVRFAULT;
+	}
+
+	uuid_generate_time(uu);
+	uuid_unparse(uu, new->fl_fsluuid);
+	strncpy(new->fl_fsnuuid, fsn_uuid, sizeof(new->fl_fsnuuid));
+	strncpy(new->fl_nsdbname, nsdb_hostname(host), sizeof(new->fl_nsdbname));
+	new->fl_nsdbport = nsdb_port(host);
+	strncpy(new->fl_fslhost, server, sizeof(new->fl_fslhost));
+	new->fl_fslport = 0;
+
+	retval = nfsref_add_nfs_fsl_defaults(rootpath, &new->fl_u.fl_nfsfsl);
+	if (retval != FEDFS_OK)
+		return retval;
+
+	*fsl = new;
+	return FEDFS_OK;
+}
+
+/**
+ * Convert command line options to a list of fedfs_fsl objects
+ *
+ * @param argv array of pointers to NUL-terminated C strings contains arguments
+ * @param optind index of "argv" where "add" subcommand arguments start
+ * @param host an initialized NSDB host object
+ * @param fsn_uuid NUL-terminated C string containing FSN UUID to use
+ * @param fsls OUT a list of fedfs_fsl objects
+ * @return a FedFsStatus code
+ *
+ * If nfsref_add_build_fsl_list() returns FEDFS_OK, caller must free the
+ * returned list of fsls with nsdb_free_fedfs_fsls().
+ *
+ */
+static FedFsStatus
+nfsref_add_build_fsl_list(char **argv, int optind, nsdb_t host,
+		const char *fsn_uuid, struct fedfs_fsl **fsls)
+{
+	struct fedfs_fsl *fsl, *result = NULL;
+	FedFsStatus retval;
+	int i;
+
+	for (i = optind + 2; argv[i] != NULL; i += 2) {
+		retval = nfsref_add_build_fsl(fsn_uuid, host,
+						argv[i], argv[i + 1], &fsl);
+		if (retval != FEDFS_OK) {
+			nsdb_free_fedfs_fsls(result);
+			return retval;
+		}
+		if (result == NULL)
+			result = fsl;
+		else
+			result->fl_next = fsl;
+	}
+	if (result == NULL)
+		return FEDFS_ERR_INVAL;
+
+	*fsls = result;
+	return FEDFS_OK;
+}
+
+/**
+ * Set up FedFS FSLs, create a FedFS junction
+ *
+ * @param junct_path NUL-terminated C string containing pathname of junction
+ * @param host an initialized and open NSDB object
+ * @param nce NUL-terminated C string containing DN of NSDB container entry
+ * @param fsn_uuid NUL-terminated C string containing FSN UUID to use
+ * @param fsls list of fedfs_fsl objects to create on NSDB
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfsref_add_create_fsls(const char *junct_path, nsdb_t host, const char *nce,
+		const char *fsn_uuid, struct fedfs_fsl *fsls)
+{
+	unsigned int ldap_err;
+	FedFsStatus retval;
+
+	retval = nsdb_create_fsls_s(host, nce, fsls, &ldap_err);
+	switch (retval) {
+	case FEDFS_OK:
+		break;
+	case FEDFS_ERR_NSDB_NONCE:
+		xlog(L_ERROR, "NCE %s does not exist\n", nce);
+		return retval;
+	case FEDFS_ERR_NSDB_LDAP_VAL:
+		xlog(L_ERROR, "Failed to create FSL records: %s\n",
+			ldap_err2string(ldap_err));
+		return retval;
+	default:
+		xlog(D_GENERAL, "%s: Failed to create FSL records: %s\n",
+			__func__, nsdb_display_fedfsstatus(retval));
+		return retval;
+	}
+
+	retval = fedfs_add_junction(junct_path, fsn_uuid, host);
+	switch (retval) {
+	case FEDFS_OK:
+		break;
+	case FEDFS_ERR_EXIST:
+		xlog(L_ERROR, "%s already contains junction metadata",
+			junct_path);
+		break;
+	default:
+		xlog(L_ERROR, "Failed to FedFS junction: %s\n",
+			nsdb_display_fedfsstatus(retval));
+	}
+	return retval;
+}
+
+/**
+ * Set up FedFS FSLs, a FedFS FSN, and add it to a local junction
+ *
+ * @param junct_path NUL-terminated C string containing pathname of junction
+ * @param argv array of pointers to NUL-terminated C strings contains arguments
+ * @param optind index of "argv" where "add" subcommand arguments start
+ * @param host an initialized and open NSDB object
+ * @param nce NUL-terminated C string containing DN of NSDB container entry
+ * @param fsn_uuid NUL-terminated C string containing FSN UUID to use
+ * @return a FedFsStatus code
+ */
+static FedFsStatus
+nfsref_add_create_fedfs_junction(const char *junct_path, char **argv, int optind,
+		nsdb_t host, const char *nce, const char *fsn_uuid)
+{
+	struct fedfs_fsl *fsls;
+	FedFsStatus retval;
+
+	retval = nfsref_add_build_fsl_list(argv, optind, host, fsn_uuid, &fsls);
+	if (retval != FEDFS_OK)
+		return retval;
+
+	retval = nfsref_add_create_fsls(junct_path, host, nce, fsn_uuid, fsls);
+	nsdb_free_fedfs_fsls(fsls);
+	return retval;
+}
+
+/**
+ * Set up FedFS FSLs, a FedFS FSN, and add it to a local junction
+ *
+ * @param junct_path NUL-terminated C string containing pathname of junction
+ * @param argv array of pointers to NUL-terminated C strings contains arguments
+ * @param optind index of "argv" where "add" subcommand arguments start
+ * @param host an initialized and open NSDB object
+ * @param nce NUL-terminated C string containing DN of NSDB container entry
+ * @return program exit status
+ */
+static int
+nfsref_add_nfs_fedfs_junction(const char *junct_path, char **argv, int optind,
+		nsdb_t host, const char *nce)
+{
+	char *fsn_uuid = NULL;
+	FedFsStatus retval;
+
+	retval = nfsref_add_create_fedfs_fsn(host, nce, &fsn_uuid);
+	switch (retval) {
+	case FEDFS_OK:
+		break;
+	case FEDFS_ERR_INVAL:
+		xlog(L_ERROR, "Missing arguments or environment variables");
+		return EXIT_FAILURE;
+	case FEDFS_ERR_SVRFAULT:
+		xlog(L_ERROR, "No memory");
+		return EXIT_FAILURE;
+	default:
+		xlog(L_ERROR, "Failed to add FedFS junction to %s: %s",
+			junct_path, nsdb_display_fedfsstatus(retval));
+		return EXIT_FAILURE;
+	}
+
+	retval = nfsref_add_create_fedfs_junction(junct_path, argv, optind,
+							host, nce, fsn_uuid);
+	if (retval != FEDFS_OK) {
+		unsigned int ldap_err;
+		nsdb_delete_fsn_s(host, nce, fsn_uuid, false, &ldap_err);
+		free(fsn_uuid);
+		return EXIT_FAILURE;
+	}
+
+	printf("Created junction %s\n", junct_path);
+	free(fsn_uuid);
+	return EXIT_SUCCESS;
+}
+
+/**
+ * Set up FedFS FSLs, a FedFS FSN, and add it to a local junction
+ *
+ * @param junct_path NUL-terminated C string containing pathname of junction
+ * @param argv array of pointers to NUL-terminated C strings contains arguments
+ * @param optind index of "argv" where "add" subcommand arguments start
+ * @return program exit status
+ */
+static int
+nfsref_add_nfs_fedfs(const char *junct_path, char **argv, int optind)
+{
+	char *binddn, *bindpw, *nsdbname, *nce;
+	unsigned short nsdbport;
+	unsigned int ldap_err;
+	FedFsStatus retval;
+	nsdb_t host = NULL;
+	int status = EXIT_FAILURE;
+
+	xlog(D_GENERAL, "%s: Adding FedFS junction to %s",
+		__func__, junct_path);
+
+	nsdb_env(&nsdbname, &nsdbport, &binddn, &nce, &bindpw);
+	if (nsdbname == NULL) {
+		xlog(L_ERROR, "Cannot determine NSDB hostname");
+		return FEDFS_ERR_INVAL;
+	}
+
+	retval = nsdb_lookup_nsdb(nsdbname, nsdbport, &host, NULL);
+	switch (retval) {
+	case FEDFS_OK:
+		break;
+	case FEDFS_ERR_NSDB_PARAMS:
+		xlog(L_ERROR, "No connection parameters for NSDB %s:%u\n",
+			nsdbname, nsdbport);
+		goto out;
+	default:
+		xlog(L_ERROR, "Failed to look up NSDB %s:%u: %s\n",
+			nsdbname, nsdbport, nsdb_display_fedfsstatus(retval));
+		goto out;
+	}
+	retval = FEDFS_ERR_INVAL;
+	if (binddn == NULL)
+		binddn = (char *)nsdb_default_binddn(host);
+	if (binddn == NULL) {
+		xlog(L_ERROR, "No NSDB bind DN was specified");
+		goto out_free;
+	}
+	if (nce == NULL)
+		nce = (char *)nsdb_default_nce(host);
+	if (nce == NULL) {
+		xlog(L_ERROR, "No NCE was specified");
+		goto out_free;
+	}
+
+	retval = nsdb_open_nsdb(host, binddn, bindpw, &ldap_err);
+	switch (retval) {
+	case FEDFS_OK:
+		break;
+	case FEDFS_ERR_NSDB_CONN:
+		xlog(L_ERROR, "Failed to connect to NSDB %s:%u",
+			nsdbname, nsdbport);
+		goto out_free;
+	case FEDFS_ERR_NSDB_AUTH:
+		xlog(L_ERROR, "Failed to authenticate to NSDB %s:%u",
+			nsdbname, nsdbport);
+		goto out_free;
+	case FEDFS_ERR_NSDB_LDAP_VAL:
+		xlog(L_ERROR, "Failed to authenticate to NSDB %s:%u: %s",
+			nsdbname, nsdbport, ldap_err2string(ldap_err));
+		goto out_free;
+	default:
+		xlog(L_ERROR, "Failed to bind to NSDB %s:%u: %s",
+			nsdbname, nsdbport, nsdb_display_fedfsstatus(retval));
+		goto out_free;
+	}
+
+	status = nfsref_add_nfs_fedfs_junction(junct_path, argv, optind,
+								host, nce);
+
+	nsdb_close_nsdb(host);
+out_free:
+	nsdb_free_nsdb(host);
+out:
+	return status;
+}
+
+/**
+ * Add locations to a junction
+ *
+ * @param type type of junction to add
+ * @param junct_path NUL-terminated C string containing pathname of junction
+ * @param argv array of pointers to NUL-terminated C strings contains arguments
+ * @param optind index of "argv" where "add" subcommand arguments start
+ * @return program exit status
+ */
+int
+nfsref_add(enum nfsref_type type, const char *junct_path, char **argv, int optind)
+{
+	switch (type) {
+	case NFSREF_TYPE_UNSPECIFIED:
+	case NFSREF_TYPE_NFS_BASIC:
+		return nfsref_add_nfs_basic(junct_path, argv, optind);
+	case NFSREF_TYPE_NFS_FEDFS:
+		return nfsref_add_nfs_fedfs(junct_path, argv, optind);
+	default:
+		xlog(L_ERROR, "Unrecognized junction type");
+	}
+	return EXIT_FAILURE;
+}
diff --git a/src/nfsref/lookup.c b/src/nfsref/lookup.c
new file mode 100644
index 0000000..861a4d0
--- /dev/null
+++ b/src/nfsref/lookup.c
@@ -0,0 +1,410 @@ 
+/**
+ * @file src/nfsref/lookup.c
+ * @brief Examine junction metadata from a local file system object
+ */
+
+/*
+ * 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 <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "fedfs.h"
+#include "junction.h"
+#include "nsdb.h"
+#include "xlog.h"
+#include "nfsref.h"
+
+/**
+ * Convert a boolean value into a displayable string constant
+ *
+ * @param value boolean value
+ * @return NUL-terminated static constant C string
+ */
+static const char *
+nfsref_lookup_display_boolean(_Bool value)
+{
+	return value ? "true" : "false";
+}
+
+/**
+ * Display a single NFS location
+ *
+ * @param fsloc pointer to an NFS location structure
+ */
+static void
+nfsref_lookup_display_nfs_location(struct nfs_fsloc *fsloc)
+{
+	char *rootpath;
+
+	if (nsdb_path_array_to_posix(fsloc->nfl_rootpath, &rootpath) == FEDFS_OK) {
+		printf("%s:%s\n", fsloc->nfl_hostname, rootpath);
+		free(rootpath);
+	} else
+		printf("%s: - Invalid root path -\n", fsloc->nfl_hostname);
+	printf("\n");
+
+	printf("\tNFS Version:\t%d.%d\n",
+		fsloc->nfl_majorver, fsloc->nfl_minorver);
+	printf("\tNFS port:\t%u\n", fsloc->nfl_hostport);
+	printf("\tValid for:\t%d\n", fsloc->nfl_validfor);
+	printf("\tCache TTL:\t%d\n", fsloc->nfl_ttl);
+	printf("\tCurrency:\t%d\n", fsloc->nfl_currency);
+	printf("\tFlags:\t\tvarsub(%s)\n",
+		nfsref_lookup_display_boolean(fsloc->nfl_flags.nfl_varsub));
+
+	printf("\tGenFlags:\twritable(%s), going(%s), split(%s)\n",
+		nfsref_lookup_display_boolean(fsloc->nfl_genflags.nfl_writable),
+		nfsref_lookup_display_boolean(fsloc->nfl_genflags.nfl_going),
+		nfsref_lookup_display_boolean(fsloc->nfl_genflags.nfl_split));
+	printf("\tTransFlags:\trdma(%s)\n",
+		nfsref_lookup_display_boolean(fsloc->nfl_transflags.nfl_rdma));
+
+	printf("\tClass:\t\tsimul(%u), handle(%u), fileid(%u)\n",
+		fsloc->nfl_info.nfl_simul,
+		fsloc->nfl_info.nfl_handle,
+		fsloc->nfl_info.nfl_fileid);
+	printf("\tClass:\t\twritever(%u), change(%u), readdir(%u)\n",
+		fsloc->nfl_info.nfl_writever,
+		fsloc->nfl_info.nfl_change,
+		fsloc->nfl_info.nfl_readdir);
+	printf("\tRead:\t\trank(%u), order(%u)\n",
+		fsloc->nfl_info.nfl_readrank, fsloc->nfl_info.nfl_readorder);
+	printf("\tWrite:\t\trank(%u), order(%u)\n",
+		fsloc->nfl_info.nfl_writerank, fsloc->nfl_info.nfl_writeorder);
+
+	printf("\n");
+}
+
+/**
+ * Display a list of NFS locations
+ *
+ * @param fslocs list of NFS locations to display
+ */
+static void
+nfsref_lookup_display_nfs_locations(struct nfs_fsloc *fslocs)
+{
+	struct nfs_fsloc *fsloc;
+
+	for (fsloc = fslocs; fsloc != NULL; fsloc = fsloc->nfl_next)
+		nfsref_lookup_display_nfs_location(fsloc);
+}
+
+/**
+ * List NFS locations in an nfs-basic junction
+ *
+ * @param junct_path NUL-terminated C string containing pathname of junction
+ * @return program exit status
+ */
+static int
+nfsref_lookup_nfs_basic(const char *junct_path)
+{
+	struct nfs_fsloc *fslocs = NULL;
+	FedFsStatus retval;
+
+	xlog(D_GENERAL, "%s: Looking up basic junction in %s",
+		__func__, junct_path);
+
+	retval = nfs_is_junction(junct_path);
+	switch (retval) {
+	case FEDFS_OK:
+		break;
+	case FEDFS_ERR_NOTJUNCT:
+		xlog(L_ERROR, "%s is not an nfs-basic junction", junct_path);
+		return EXIT_FAILURE;
+	default:
+		xlog(L_ERROR, "Failed to access %s: %s",
+			junct_path, nsdb_display_fedfsstatus(retval));
+		return EXIT_FAILURE;
+	}
+
+	retval = nfs_get_locations(junct_path, &fslocs);
+	if (retval != FEDFS_OK) {
+		xlog(L_ERROR, "Failed to access %s: %s",
+			junct_path, nsdb_display_fedfsstatus(retval));
+		return EXIT_FAILURE;
+	}
+
+	nfsref_lookup_display_nfs_locations(fslocs);
+
+	nfs_free_locations(fslocs);
+	return EXIT_SUCCESS;
+}
+
+/**
+ * Convert a boolean value into a displayable string constant (LDAP style)
+ *
+ * @param value boolean value
+ * @return NUL-terminated static constant C string
+ */
+static const char *
+nfsref_lookup_display_ldap_boolean(_Bool value)
+{
+	return value ? "TRUE" : "FALSE";
+}
+
+/**
+ * Display nfs_fsl portion of a fedfs_fsl structure
+ *
+ * @param nfsl pointer to a fedfs_nfs_fsl structure
+ */
+static void
+nfsref_lookup_display_fedfs_nfs_fsl(struct fedfs_nfs_fsl *nfsl)
+{
+	char *rootpath;
+
+	if (nsdb_path_array_to_posix(nfsl->fn_nfspath, &rootpath) == FEDFS_OK) {
+		printf("\tfedfsNfsPath:\t\t\t%s\n", rootpath);
+		free(rootpath);
+	} else
+		printf("\tfedfsNfsPath:\t\t\tInvalid\n");
+
+	printf("\tfedfsNfsMajorVer:\t\t%d\n", nfsl->fn_majorver);
+	printf("\tfedfsNfsMinorVer:\t\t%d\n", nfsl->fn_minorver);
+	printf("\tfedfsNfsCurrency:\t\t%d\n", nfsl->fn_currency);
+	printf("\tfedfsNfsGenFlagWritable:\t%s\n",
+		nfsref_lookup_display_ldap_boolean(nfsl->fn_gen_writable));
+	printf("\tfedfsNfsGenFlagGoing:\t\t%s\n",
+		nfsref_lookup_display_ldap_boolean(nfsl->fn_gen_going));
+	printf("\tfedfsNfsGenFlagSplit:\t\t%s\n",
+		nfsref_lookup_display_ldap_boolean(nfsl->fn_gen_split));
+	printf("\tfedfsNfsTransFlagRdma:\t\t%s\n",
+		nfsref_lookup_display_ldap_boolean(nfsl->fn_trans_rdma));
+	printf("\tfedfsNfsClassSimul:\t\t%u\n", nfsl->fn_class_simul);
+	printf("\tfedfsNfsClassHandle:\t\t%u\n", nfsl->fn_class_handle);
+	printf("\tfedfsNfsClassFileid:\t\t%u\n", nfsl->fn_class_fileid);
+	printf("\tfedfsNfsClassWritever:\t\t%u\n", nfsl->fn_class_writever);
+	printf("\tfedfsNfsClassChange:\t\t%u\n", nfsl->fn_class_change);
+	printf("\tfedfsNfsClassReaddir:\t\t%u\n", nfsl->fn_class_readdir);
+	printf("\tfedfsNfsReadRank:\t\t%d\n", nfsl->fn_readrank);
+	printf("\tfedfsNfsReadOrder:\t\t%d\n", nfsl->fn_readorder);
+	printf("\tfedfsNfsWriteRank:\t\t%d\n", nfsl->fn_writerank);
+	printf("\tfedfsNfsWriteOrder:\t\t%d\n", nfsl->fn_writeorder);
+	printf("\tfedfsNfsVarSub:\t\t\t%s\n",
+		nfsref_lookup_display_ldap_boolean(nfsl->fn_varsub));
+	printf("\tfedfsNfsValidFor:\t\t%d\n", nfsl->fn_validfor);
+}
+
+/**
+ * Display a single FedFS fileset location
+ *
+ * @param fsl pointer to a fedfs_fsl structure
+ */
+static void
+nfsref_lookup_display_fedfs_fsl(struct fedfs_fsl *fsl)
+{
+	int i;
+
+	printf("FedFS Fileset Location:\n");
+
+	printf("\tfedfsFslUuid:\t\t\t%s\n", fsl->fl_fsluuid);
+	printf("\tfedfsFsnUuid:\t\t\t%s\n", fsl->fl_fsnuuid);
+	printf("\tfedfsNsdbName:\t\t\t%s\n", fsl->fl_nsdbname);
+	printf("\tfedfsNsdbPort:\t\t\t%u\n", fsl->fl_nsdbport);
+	printf("\tfedfsFslHost:\t\t\t%s\n", fsl->fl_fslhost);
+	printf("\tfedfsFslPort:\t\t\t%u\n", fsl->fl_fslport);
+	printf("\tfedfsFslTTL:\t\t\t%d\n", fsl->fl_fslttl);
+
+	if (fsl->fl_annotations != NULL) {
+		for (i = 0; fsl->fl_annotations[i] != NULL; i++)
+			printf("\tfedfsAnnotation[%d]: %s\n", i,
+				fsl->fl_annotations[i]);
+	}
+
+	if (fsl->fl_description != NULL) {
+		for (i = 0; fsl->fl_description[i] != NULL; i++)
+			printf("\tfedfsDescr[%d]: %s\n", i,
+				fsl->fl_description[i]);
+	}
+
+	switch (fsl->fl_type) {
+	case FEDFS_NFS_FSL:
+		nfsref_lookup_display_fedfs_nfs_fsl(&fsl->fl_u.fl_nfsfsl);
+		break;
+	default:
+		printf("\tUnknown FedFS FSL type\n");
+	}
+
+	printf("\n");
+	fflush(stdout);
+}
+
+/**
+ * Display a list of FedFS fileset locations
+ *
+ * @param fsls list of FedFS fileset locations to display
+ */
+static void
+nfsref_lookup_display_fedfs_fsls(struct fedfs_fsl *fsls)
+{
+	struct fedfs_fsl *fsl;
+
+	for (fsl = fsls; fsl != NULL; fsl = fsl->fl_next)
+		nfsref_lookup_display_fedfs_fsl(fsl);
+}
+
+/**
+ * Resolve a FedFS fileset name
+ *
+ * @param fsn_uuid NUL-terminated C string containing FSN UUID to resolve
+ * @param host an initialized NSDB handle
+ * @return program exit status
+ */
+static int
+nfsref_lookup_resolve_fsn(const char *fsn_uuid, nsdb_t host)
+{
+	int status = EXIT_FAILURE;
+	struct fedfs_fsl *fsls;
+	unsigned int ldap_err;
+	FedFsStatus retval;
+
+	xlog(D_GENERAL, "%s: resolving FSN UUID %s with NSDB %s:%u",
+		__func__, fsn_uuid, nsdb_hostname(host), nsdb_port(host));
+
+	if (nsdb_open_nsdb(host, NULL, NULL, &ldap_err) != FEDFS_OK)
+		return status;
+
+	retval = nsdb_resolve_fsn_s(host, NULL, fsn_uuid, &fsls, &ldap_err);
+	switch (retval) {
+	case FEDFS_OK:
+		nfsref_lookup_display_fedfs_fsls(fsls);
+		nsdb_free_fedfs_fsls(fsls);
+		status = EXIT_SUCCESS;
+		break;
+	case FEDFS_ERR_NSDB_NOFSL:
+		xlog(L_ERROR, "%s: No FSL entries for FSN %s",
+			__func__, fsn_uuid);
+		break;
+	case FEDFS_ERR_NSDB_NOFSN:
+		xlog(L_ERROR, "%s: No FSN %s found",
+			__func__, fsn_uuid);
+		break;
+	case FEDFS_ERR_NSDB_LDAP_VAL:
+		xlog(L_ERROR, "%s: NSDB operation failed with %s",
+			__func__, ldap_err2string(ldap_err));
+		break;
+	default:
+		xlog(L_ERROR, "%s: Failed to resolve FSN %s: %s",
+			__func__, fsn_uuid, nsdb_display_fedfsstatus(status));
+	}
+
+	nsdb_close_nsdb(host);
+	return status;
+}
+
+/**
+ * Resolve a local FedFS-style junction
+ *
+ * @param junct_path NUL-terminated C string containing pathname of junction
+ * @return program exit status
+ */
+static int
+nfsref_lookup_nfs_fedfs(const char *junct_path)
+{
+	FedFsStatus retval;
+	char *fsn_uuid;
+	nsdb_t host;
+	int status;
+
+	xlog(D_GENERAL, "%s: Looking up FedFS junction in %s",
+		__func__, junct_path);
+
+	retval = fedfs_is_junction(junct_path);
+	switch (retval) {
+	case FEDFS_OK:
+		break;
+	case FEDFS_ERR_NOTJUNCT:
+		xlog(L_ERROR, "%s is not an nfs-fedfs junction", junct_path);
+		return EXIT_FAILURE;
+	default:
+		xlog(L_ERROR, "Failed to access %s: %s",
+			junct_path, nsdb_display_fedfsstatus(retval));
+		return EXIT_FAILURE;
+	}
+
+	retval = fedfs_get_fsn(junct_path, &fsn_uuid, &host);
+	if (retval != FEDFS_OK) {
+		xlog(L_ERROR, "Failed to access %s: %s",
+			junct_path, nsdb_display_fedfsstatus(retval));
+		return EXIT_FAILURE;
+	}
+
+	status = nfsref_lookup_resolve_fsn(fsn_uuid, host);
+
+	free(fsn_uuid);
+	nsdb_free_nsdb(host);
+	return status;
+}
+
+/**
+ * Resolve either a FedFS or NFS basic junction
+ *
+ * @param junct_path NUL-terminated C string containing pathname of junction
+ * @return program exit status
+ */
+static int
+nfsref_lookup_unspecified(const char *junct_path)
+{
+	FedFsStatus retval;
+
+	retval = nfs_is_junction(junct_path);
+	if (retval == FEDFS_OK)
+		return nfsref_lookup_nfs_basic(junct_path);
+	if (retval != FEDFS_ERR_NOTJUNCT) {
+		xlog(L_ERROR, "Failed to access %s: %s",
+			junct_path, nsdb_display_fedfsstatus(retval));
+		return EXIT_FAILURE;
+	}
+	retval = fedfs_is_junction(junct_path);
+	if (retval == FEDFS_OK)
+		return nfsref_lookup_nfs_fedfs(junct_path);
+	if (retval != FEDFS_ERR_NOTJUNCT) {
+		xlog(L_ERROR, "Failed to access %s: %s",
+			junct_path, nsdb_display_fedfsstatus(retval));
+		return EXIT_FAILURE;
+	}
+	xlog(L_ERROR, "%s is not a junction", junct_path);
+	return EXIT_FAILURE;
+}
+
+/**
+ * Enumerate metadata of a junction
+ *
+ * @param type type of junction to add
+ * @param junct_path NUL-terminated C string containing pathname of junction
+ * @return program exit status
+ */
+int
+nfsref_lookup(enum nfsref_type type, const char *junct_path)
+{
+	switch (type) {
+	case NFSREF_TYPE_UNSPECIFIED:
+		return nfsref_lookup_unspecified(junct_path);
+	case NFSREF_TYPE_NFS_BASIC:
+		return nfsref_lookup_nfs_basic(junct_path);
+	case NFSREF_TYPE_NFS_FEDFS:
+		return nfsref_lookup_nfs_fedfs(junct_path);
+	default:
+		xlog(L_ERROR, "Unrecognized junction type");
+	}
+	return EXIT_FAILURE;
+}
diff --git a/src/nfsref/nfsref.c b/src/nfsref/nfsref.c
new file mode 100644
index 0000000..be05bda
--- /dev/null
+++ b/src/nfsref/nfsref.c
@@ -0,0 +1,187 @@ 
+/**
+ * @file src/nfsref/nfsref.c
+ * @brief Manage NFS referrals
+ */
+
+/*
+ * 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/capability.h>
+#include <sys/prctl.h>
+#include <sys/stat.h>
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <time.h>
+
+#include <locale.h>
+#include <langinfo.h>
+
+#include "fedfs.h"
+#include "nsdb.h"
+#include "junction.h"
+#include "xlog.h"
+#include "gpl-boiler.h"
+#include "nfsref.h"
+
+/**
+ * Short form command line options
+ */
+static const char nfsref_opts[] = "?dt:";
+
+/**
+ * Long form command line options
+ */
+static const struct option nfsref_longopts[] = {
+	{ "debug", 0, NULL, 'd', },
+	{ "help", 0, NULL, '?', },
+	{ "type", 1, NULL, 't', },
+	{ NULL, 0, NULL, 0, },
+};
+
+/**
+ * Display program synopsis
+ *
+ * @param progname NUL-terminated C string containing name of program
+ */
+static void
+nfsref_usage(const char *progname)
+{
+	fprintf(stderr, "\n%s: version " VERSION "\n", progname);
+	fprintf(stderr, "Usage: %s [ -t type ] SUBCOMMAND [ ARGUMENTS ]\n\n",
+		progname);
+
+	fprintf(stderr, "SUBCOMMAND is one of:\n");
+	fprintf(stderr, "\tadd        Add a new junction\n");
+	fprintf(stderr, "\tremove     Remove an existing junction\n");
+	fprintf(stderr, "\tlookup     Enumerate a junction\n");
+
+	fprintf(stderr, "\nUse \"%s SUBCOMMAND -?\" for details.\n", progname);
+
+	fprintf(stderr, "%s", fedfs_gpl_boilerplate);
+}
+
+/**
+ * Program entry point
+ *
+ * @param argc count of command line arguments
+ * @param argv array of NUL-terminated C strings containing command line arguments
+ * @return program exit status
+ */
+int
+main(int argc, char **argv)
+{
+	char *progname, *subcommand, *junct_path;
+	enum nfsref_type type;
+	int arg, exit_status;
+
+	(void)umask(S_IWGRP | S_IWOTH);
+
+	exit_status = EXIT_FAILURE;
+
+	/* Ensure UTF-8 strings can be handled transparently */
+	if (setlocale(LC_CTYPE, "") == NULL ||
+	    strcmp(nl_langinfo(CODESET), "UTF-8") != 0) {
+		fprintf(stderr, "Failed to set locale and langinfo\n");
+		goto out;
+	}
+
+	/* Set the basename */
+	if ((progname = strrchr(argv[0], '/')) != NULL)
+		progname++;
+	else
+		progname = argv[0];
+
+	xlog_stderr(1);
+	xlog_syslog(0);
+	xlog_open(progname);
+
+	if (argc < 2) {
+		nfsref_usage(progname);
+		goto out;
+	}
+
+	type = NFSREF_TYPE_UNSPECIFIED;
+	while ((arg = getopt_long(argc, argv, nfsref_opts,
+			nfsref_longopts, NULL)) != -1) {
+		switch (arg) {
+		case 'd':
+			xlog_config(D_ALL, 1);
+			break;
+		case 't':
+			if (strcmp(optarg, "nfs-basic") == 0)
+				type = NFSREF_TYPE_NFS_BASIC;
+			else if (strcmp(optarg, "nfs-fedfs") == 0)
+				type = NFSREF_TYPE_NFS_FEDFS;
+			else {
+				fprintf(stderr,
+					"Unrecognized junction type: %s",
+					optarg);
+				exit(EXIT_FAILURE);
+			}
+			break;
+		case '?':
+			nfsref_usage(progname);
+			exit(EXIT_SUCCESS);
+		}
+	}
+	if (argc < optind + 2) {
+		fprintf(stderr, "Not enough positional parameters\n");
+		nfsref_usage(progname);
+		goto out;
+	}
+
+	if (geteuid() != 0) {
+		fprintf(stderr, "Root permission is required\n");
+		goto out;
+	}
+
+	subcommand = argv[optind];
+	junct_path = argv[optind + 1];
+
+	if (strcasecmp(subcommand, "add") == 0) {
+		if (argc < optind + 3) {
+			fprintf(stderr, "Not enough positional parameters\n");
+			nfsref_usage(progname);
+			goto out;
+		}
+		exit_status = nfsref_add(type, junct_path, argv, optind);
+		if (exit_status == EXIT_SUCCESS)
+			(void)junction_flush_exports_cache();
+	} else if (strcasecmp(subcommand, "remove") == 0) {
+		exit_status = nfsref_remove(type, junct_path);
+		if (exit_status == EXIT_SUCCESS)
+			(void)junction_flush_exports_cache();
+	} else if (strcasecmp(subcommand, "lookup") == 0)
+		exit_status = nfsref_lookup(type, junct_path);
+	else {
+		xlog(L_ERROR, "Unrecognized subcommand: %s", subcommand);
+		nfsref_usage(progname);
+	}
+
+out:
+	exit(exit_status);
+}
diff --git a/src/nfsref/nfsref.h b/src/nfsref/nfsref.h
new file mode 100644
index 0000000..8e40fd9
--- /dev/null
+++ b/src/nfsref/nfsref.h
@@ -0,0 +1,43 @@ 
+/**
+ * @file src/nfsref/nfsref.h
+ * @brief Declarations and definitions for nfsref command line tool
+ */
+
+/*
+ * 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
+ */
+
+#ifndef FEDFS_NFSREF_H
+#define FEDFS_NFSREF_H
+
+/**
+ * Junction types supported by the "nfsref" command
+ */
+enum nfsref_type {
+	NFSREF_TYPE_UNSPECIFIED = 1,
+	NFSREF_TYPE_NFS_BASIC,
+	NFSREF_TYPE_NFS_FEDFS
+};
+
+int	 nfsref_add(enum nfsref_type type, const char *junct_path, char **argv,
+				int optind);
+int	 nfsref_remove(enum nfsref_type type, const char *junct_path);
+int	 nfsref_lookup(enum nfsref_type type, const char *junct_path);
+
+#endif	/* !FEDFS_NFSREF_H */
diff --git a/src/nfsref/remove.c b/src/nfsref/remove.c
new file mode 100644
index 0000000..a0b46f8
--- /dev/null
+++ b/src/nfsref/remove.c
@@ -0,0 +1,163 @@ 
+/**
+ * @file src/nfsref/remove.c
+ * @brief Remove junction metadata from a local file system object
+ */
+
+/*
+ * 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 <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <unistd.h>
+#include <errno.h>
+
+#include "fedfs.h"
+#include "junction.h"
+#include "xlog.h"
+#include "nfsref.h"
+
+/**
+ * Remove any NFS junction information
+ *
+ *
+ * @param junct_path NUL-terminated C string containing pathname of junction
+ * @return program exit status
+ */
+static int
+nfsref_remove_unspecified(const char *junct_path)
+{
+	FedFsStatus retval;
+
+	xlog(D_GENERAL, "%s: Removing junction from %s",
+		__func__, junct_path);
+
+	retval = nfs_delete_junction(junct_path);
+	if (retval != FEDFS_OK) {
+		if (retval != FEDFS_ERR_NOTJUNCT)
+			goto out_err;
+		retval = fedfs_delete_junction(junct_path);
+		if (retval != FEDFS_OK)
+			goto out_err;
+	}
+
+	printf("Removed junction from %s\n", junct_path);
+	return EXIT_SUCCESS;
+
+out_err:
+	switch (retval) {
+	case FEDFS_ERR_NOTJUNCT:
+		xlog(L_ERROR, "No junction information found in %s", junct_path);
+		break;
+	default:
+		xlog(L_ERROR, "Failed to delete %s: %s",
+			junct_path, nsdb_display_fedfsstatus(retval));
+	}
+	return EXIT_FAILURE;
+}
+
+/**
+ * Remove an NFS locations-style junction
+ *
+ * @param junct_path NUL-terminated C string containing pathname of junction
+ * @return program exit status
+ */
+static int
+nfsref_remove_nfs_basic(const char *junct_path)
+{
+	int status = EXIT_FAILURE;
+	FedFsStatus retval;
+
+	xlog(D_GENERAL, "%s: Removing FedFS junction from %s",
+		__func__, junct_path);
+
+	retval = nfs_delete_junction(junct_path);
+	switch (retval) {
+	case FEDFS_OK:
+		printf("Removed nfs-basic junction from %s\n", junct_path);
+		status = EXIT_SUCCESS;
+		break;
+	case FEDFS_ERR_NOTJUNCT:
+		xlog(L_ERROR, "%s is not an nfs-basic junction", junct_path);
+		break;
+	default:
+		xlog(L_ERROR, "Failed to delete %s: %s",
+			junct_path, nsdb_display_fedfsstatus(retval));
+	}
+
+	return status;
+}
+
+/**
+ * Remove a FedFS-style junction
+ *
+ * @param junct_path NUL-terminated C string containing pathname of junction
+ * @return program exit status
+ */
+static int
+nfsref_remove_nfs_fedfs(const char *junct_path)
+{
+	int status = EXIT_FAILURE;
+	FedFsStatus retval;
+
+	xlog(D_GENERAL, "%s: Removing FedFS junction from %s",
+		__func__, junct_path);
+
+	retval = fedfs_delete_junction(junct_path);
+	switch (retval) {
+	case FEDFS_OK:
+		printf("Removed nfs-fedfs junction from %s\n", junct_path);
+		status = EXIT_SUCCESS;
+		break;
+	case FEDFS_ERR_NOTJUNCT:
+		xlog(L_ERROR, "%s is not an nfs-fedfs junction", junct_path);
+		break;
+	default:
+		xlog(L_ERROR, "Failed to delete %s: %s",
+			junct_path, nsdb_display_fedfsstatus(retval));
+	}
+
+	return status;
+}
+
+/**
+ * Remove an NFS junction
+ *
+ * @param type type of junction to add
+ * @param junct_path NUL-terminated C string containing pathname of junction
+ * @return program exit status
+ */
+int
+nfsref_remove(enum nfsref_type type, const char *junct_path)
+{
+	switch (type) {
+	case NFSREF_TYPE_UNSPECIFIED:
+		return nfsref_remove_unspecified(junct_path);
+	case NFSREF_TYPE_NFS_BASIC:
+		return nfsref_remove_nfs_basic(junct_path);
+	case NFSREF_TYPE_NFS_FEDFS:
+		return nfsref_remove_nfs_fedfs(junct_path);
+	default:
+		xlog(L_ERROR, "Unrecognized junction type");
+	}
+	return EXIT_FAILURE;
+}