/*
 * Copyright (c) 2000-2002 Silicon Graphics, Inc.
 * All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it would 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write the Free Software Foundation,
 * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/param.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <utime.h>
#include <limits.h>
#include <time.h>
#include <xfs/handle.h>
#include <dirent.h>
#include <sys/ioctl.h>
#include <assert.h>
#include <string.h>
#include <uuid/uuid.h>
#include <xfs/xfs.h>

#include "config.h"

#include "types.h"
#include "exit.h"
#include "cldmgr.h"
#include "path.h"
#include "openutil.h"
#include "getopt.h"
#include "stream.h"
#include "mlog.h"
#include "dlog.h"
#include "global.h"
#include "drive.h"
#include "media.h"
#include "content.h"
#include "content_inode.h"
#include "inomap.h"
#include "namreg.h"
#include "dirattr.h"
#include "bag.h"
#include "node.h"
#include "tree.h"
#include "libgen.h"
#include "mmap.h"
#include "exit.h"

/* structure definitions used locally ****************************************/

/* name of persistent state file
 */
#define PERS_NAME	"tree"

/* orphanage specifics. ino must be otherwise unused in the dump source fs.
 * zero works.
 */
#define ORPH_INO	0
#define ORPH_NAME	"orphanage"


/* VM budgeting - give hash array one sixteenth, rest goes to node array
 */
#define HASHSZ_PERVM	16

/* reserve the first page for persistent state
 */
struct treePersStorage {
	xfs_ino_t p_rootino;
		/* ino of root
		 */
	nh_t p_rooth;
		/* handle of root node
		 */
	nh_t p_orphh;
		/* handle to orphanage node
		 */
	size64_t p_hashsz;
		/* size of hash array (private to hash abstraction)
		 */
	size_t p_hashmask;
		/* hash mask (private to hash abstraction)
		 */
	bool_t p_ownerpr;
		/* restore directory owner/group attributes
		 */
	bool_t p_fullpr;
		/* restoring a full level 0 non-resumed dump (can skip
		 * some steps)
		 */
	bool_t p_ignoreorphpr;
		/* set if positive subtree or interactive
		 */
	bool_t p_truncategenpr;
		/* truncate inode generation number (for compatibility
		 * with xfsdump format 2 and earlier)
		 */
};

typedef struct treePersStorage treepers_t;

#define PERSSZ	perssz


/* interactive dialog transient state
 */
#define INTER_ARGMAX	10	/* max number of args to interactive cmds */
struct inter {
	size_t i_argc;
	char *i_argv[INTER_ARGMAX];
	nh_t i_cwdh;
	char i_name[NAME_MAX + 1];
};

typedef struct inter inter_t;

/* transient state
 */
struct tran {
	bool_t t_toconlypr;
		/* just display table of contents; don't restore files
		 */
	char *t_hkdir;
		/* full absolute pathname of housekeeping directory
		 */
	char *t_dstdir;
		/* full absolute pathname of destination directory
		 */
	bool_t t_dstdirisxfspr;
		/* destination directory is an xfs filesystem; xfs-specific
		 * calls can be made when needed.
		 */
	char *t_orphdir;
		/* full absolute pathname of orphanage directory
		 */
	char *t_hksubtree;
		/* if non-NULL, is path of hkdir relative to dstdir.
		 * don't restore there.
		 */
	int t_persfd;
		/* file descriptor of the persistent state file
		 */
	nh_t *t_hashp;
		/* pointer to mapped hash array (private to hash abstraction)
		 */
	char t_namebuf[NAME_MAX + 1];
		/* to hold names temporarily retrieved from name registry
		 */
	inter_t t_inter;
		/* context for interactive subtree selection
		 */
};

typedef struct tran tran_t;


/* node structure. each node represents a directory entry
 */
#define NODESZ	56

struct node {
	xfs_ino_t n_ino;	/* 8  8 ino */
	nrh_t n_nrh;		/* 8 16 handle to name in name registry */
	dah_t n_dah;		/* 4 20 handle to directory attributes */
	nh_t n_hashh;		/* 4 24 hash array */
	nh_t n_parh;		/* 4 28 parent */
	nh_t n_sibh;		/* 4 32 sibling list */
	nh_t n_sibprevh;	/* 4 36 prev sibling list - dbl link list */
	nh_t n_cldh;		/* 4 40 children list */
	nh_t n_lnkh;		/* 4 44 hard link list */
	gen_t n_gen;		/* 4 48 generation count mod 0x10000 */
	u_char_t n_flags;	/* 1 49 action and state flags */
	u_char_t n_nodehkbyte;	/* 1 50 given to node abstraction */
	char n_pad[6];		/* 6 56 */
};

typedef struct node node_t;

#define NF_REAL		(1 << 0)
	/* set when the corresponding file/dir has been created in
	 * the restore destination.
	 */
#define NF_SUBTREE	(1 << 1)
	/* marks nodes in the selected subtrees.
	 */
#define NF_REFED	(1 << 2)
	/* indicates node is still referenced according to incremental/resumed
	 * dump. used to detect dirents no longer used. prior to restoring
	 * a dump session, this flag is cleared in all nodes. during the dirent
	 * restoral, it is set. it may also be set during the adjustment
	 * for referenced but undumped directories. NOTE: nodes in the
	 * orphanage NEVER have this flag set.
	 */
#define NF_WRITTEN	(1 << 3)
	/* set as soon as a non-dir node restore is begun. allows
	 * overwrite inhibit options to work with segmented files
	 */
#define NF_ISDIR	(1 << 4)
	/* indicates this node is a directory. set when a directory is taken
	 * from the dirdump.
	 */
#define NF_DUMPEDDIR	(1 << 5)
	/* indicates this node is a directory present in the current dirdump.
	 * at beginning of session, this flag is cleared in all nodes.
	 * then as each directory dump is read from media, the flag
	 * is set from the corresponding node. this allows adjustments to
	 * the NF_REFED flag: if a directory was not dumped, either it no
	 * longer exists or it has not changed. if it is referenced, we assume
	 * it exists, in which case if it is not dumped then all of its entries
	 * are referenced as well.
	 */
#define NF_NEWORPH	(1 << 6)
	/* cleared from all nodes in the orphanage before a dump is applied.
	 * set if a dir is seen in the dirdump but no node exists for it.
	 * cleared if that dir is adopted subsequently during the dirdump.
	 * set if a nondir is seen in the nondir dump but no node exists for
	 * it. in either case a node is created and placed in the orphanage.
	 * during rmdir/unlink processing, nodes so marked are left alone.
	 * since the flag is cleared at the beginning of the next increment,
	 * old orphans had better be adopted, otherwise they will be unlinked.
	 */

/* link list iterator context
 */
struct link_iter_context {
	nh_t li_headh;          /* head of hard link list */
	nh_t li_prevh;          /* next to last node returned by _next() */
	nh_t li_lasth;          /* last node returned by _next() */
	bool_t li_donepr;       /* set as soon as last.next null */
};
typedef struct link_iter_context link_iter_context_t;

/* used for caching parent pathname from previous Node2path result
 */
struct path_cache {
	nh_t nh;
	int len;
	char buf[MAXPATHLEN];
};
typedef struct path_cache path_cache_t;

/* declarations of externally defined global symbols *************************/

extern void usage(void);
extern size_t pgsz;
extern size_t pgmask;
extern bool_t restore_rootdir_permissions;
extern bool_t need_fixrootdir;

/* forward declarations of locally defined static functions ******************/

static nh_t Node_alloc(xfs_ino_t ino,
			gen_t gen,
			nrh_t nrh,
			dah_t dah,
			size_t flags);
static void Node_free(nh_t *nhp);
static node_t * Node_map(nh_t nh);
static void Node_unmap(nh_t nh, node_t **npp);
static int Node2path_recurse(nh_t nh, char *buf,
				   int bufsz, int level);
static void adopt(nh_t parh, nh_t cldh, nrh_t nrh);
static nrh_t disown(nh_t cldh);
static void selsubtree(nh_t nh, bool_t sensepr);
static void selsubtree_recurse_down(nh_t nh, bool_t sensepr);
static nh_t link_hardh(xfs_ino_t ino, gen_t gen);
static nh_t link_nexth(nh_t nh);
static nh_t link_matchh(nh_t hardh, nh_t parh, char *name);
static void link_in(nh_t nh);
static void link_out(nh_t nh);
static void link_headiter(bool_t (*cbfp)(void *contextp, nh_t hardh),
			   void *contextp);
static void link_iter_init(link_iter_context_t *link_iter_contextp,
			    nh_t hardheadh);
static nh_t link_iter_next(link_iter_context_t *link_iter_contextp);
void link_iter_unlink(link_iter_context_t *link_iter_contextp, nh_t nh);
static bool_t hash_init(size64_t vmsz,
			 size64_t dircnt,
			 size64_t nondircnt,
			 char *perspath);
static bool_t hash_sync(char *perspath);
static inline size_t hash_val(xfs_ino_t ino, size_t hashmask);
static void hash_in(nh_t nh);
static void hash_out(nh_t nh);
static nh_t hash_find(xfs_ino_t ino, gen_t gen);
static void hash_iter(bool_t (*cbfp)(void *contextp, nh_t hashh),
		       void *contextp);
static void setdirattr(dah_t dah, char *path);
static bool_t tsi_walkpath(char *arg, nh_t rooth, nh_t cwdh,
			    dlog_pcbp_t pcb, void *pctxp,
			    nh_t *namedhp, nh_t *parhp, nh_t *cldhp,
			    xfs_ino_t *inop, bool_t *isdirprp, bool_t *isselpr);
static bool_t Node2path(nh_t nh, char *path, char *errmsg);
static bool_t tree_setattr_recurse(nh_t parh, char *path);
static void tsi_cmd_pwd_recurse(void *ctxp,
				 dlog_pcbp_t pcb,
				 void *pctxp,
				 nh_t nh);
static int mkdir_r(char *path);
#ifdef TREE_CHK
static bool_t Node_chk(nh_t nh, nh_t *nexthashhp, nh_t *nextlnkhp);
static bool_t tree_chk2(void);
#endif /* TREE_CHK */

/* definition of locally defined global variables ****************************/


/* definition of locally defined static variables *****************************/

static treepers_t *persp = 0;
static tran_t *tranp = 0;
static char *persname = PERS_NAME;
static char *orphname = ORPH_NAME;
static xfs_ino_t orphino = ORPH_INO;
static nh_t orig_rooth = NH_NULL;


/* definition of locally defined global functions ****************************/

void
tree_fixroot(void)
{
	nh_t		rooth = persp->p_rooth;
	xfs_ino_t 	rootino;

	while (1) {
		nh_t	parh;
		node_t *rootp = Node_map(rooth);

		rootino = rootp->n_ino;
		parh = rootp->n_parh;
		Node_unmap(rooth, &rootp);

		if (parh == rooth ||
		/*
		 * since all new node (including non-parent)
		 * would be adopted into orphh
		 */
		    parh == persp->p_orphh ||
		    parh == NH_NULL)
			break;
		rooth = parh;
	}

	if (rooth != persp->p_rooth) {
		persp->p_rooth = rooth;
		persp->p_rootino = rootino;
		disown(rooth);
		adopt(persp->p_rooth, persp->p_orphh, NH_NULL);

		mlog(MLOG_NORMAL, _("fix root # to %llu (bind mount?)\n"),
		     rootino);
	}
}

/* ARGSUSED */
bool_t
tree_init(char *hkdir,
	   char *dstdir,
	   bool_t toconlypr,
	   bool_t ownerpr,
	   xfs_ino_t rootino,
	   xfs_ino_t firstino,
	   xfs_ino_t lastino,
	   size64_t dircnt,
	   size64_t nondircnt,
	   size64_t vmsz,
	   bool_t fullpr,
	   bool_t dstdirisxfspr,
	   uint32_t dumpformat,
	   bool_t truncategenpr)
{
	off64_t nodeoff;
	char *perspath;
	bool_t ok;
	int rval;

	/* sanity checks
	 */
	assert(!(PERSSZ % pgsz));
	assert(sizeof(persp) <= PERSSZ);
	assert(sizeof(node_t) <= NODESZ);
	assert(!persp);
	assert(!tranp);

	/* allocate transient state
	 */
	tranp = (tran_t *)calloc(1, sizeof(tran_t));
	assert(tranp);

	tranp->t_toconlypr = toconlypr;
	tranp->t_hkdir = hkdir;
	tranp->t_dstdir = dstdir;
	tranp->t_dstdirisxfspr = dstdirisxfspr;

	/* allocate a char string buffer to hold the abs. pathname
	 * of the orphanage directory file. load it with the pathname.
	 */
	tranp->t_orphdir = open_pathalloc(tranp->t_dstdir,
					   orphname,
					   0);

	/* determine if housekeeping dir is within the destination.
	 * generate a relative path containing the difference,
	 * else null. will not restore into there.
	 */
	if (strcmp(dstdir, ".")) {
		tranp->t_hksubtree = path_diff(hkdir, dstdir);
	} else {
		tranp->t_hksubtree = 0;
	}

	/* create an orphanage, if it already exists, complain.
	 * not needed if just a table-of-contents restore.
	 */
	if (!tranp->t_toconlypr) {
		rval = mkdir(tranp->t_orphdir, S_IRWXU);
		if (rval) {
			if (errno == EEXIST) {
				mlog(MLOG_NORMAL | MLOG_ERROR | MLOG_TREE, _(
				      "%s already exists: "
				      "rm -rf prior to initating restore\n"),
				      tranp->t_orphdir);
			} else {
				mlog(MLOG_NORMAL | MLOG_ERROR | MLOG_TREE, _(
				      "unable to create %s: %s\n"),
				      tranp->t_orphdir,
				      strerror(errno));
			}
			return BOOL_FALSE;
		}
	}

	/* build a full pathname to pers. state file
	 */
	perspath = open_pathalloc(tranp->t_hkdir, persname, 0);

	/* create the persistent state file
	 */
	(void)unlink(perspath);
	tranp->t_persfd = open(perspath,
				O_RDWR | O_CREAT,
				S_IRUSR | S_IWUSR);
	if (tranp->t_persfd < 0) {
		mlog(MLOG_NORMAL | MLOG_ERROR | MLOG_TREE, _(
		      "could not open %s: %s\n"),
		      perspath,
		      strerror(errno));
		return BOOL_FALSE;
	}

	/* mmap the persistent state
	 */
	assert(!(PERSSZ % pgsz));
	persp = (treepers_t *) mmap_autogrow(
				     PERSSZ,
				     tranp->t_persfd,
				     (off64_t)0);
	if (persp == (treepers_t *)-1) {
		mlog(MLOG_NORMAL | MLOG_ERROR | MLOG_TREE, _(
		      "unable to map %s: %s\n"),
		      perspath,
		      strerror(errno));
		return BOOL_FALSE;
	}

	/* create the hash abstraction. it will map more of the
	 * persistent state file.
	 */
	ok = hash_init(vmsz / HASHSZ_PERVM, dircnt, nondircnt, perspath);
	if (!ok) {
		return BOOL_FALSE;
	}

	/* initialize the node abstraction. let it's use of backing store
	 * begin immediately after the hash abstraction. give it the remainder
	 * of vm.
	 */
	assert(persp->p_hashsz <= (size64_t)(OFF64MAX - (off64_t)PERSSZ));
	nodeoff = (off64_t)PERSSZ + (off64_t)persp->p_hashsz;
	assert(vmsz > (size64_t)nodeoff);
	ok = node_init(tranp->t_persfd,
		        nodeoff,
		        NODESZ,
			 (ix_t)offsetofmember(node_t, n_nodehkbyte),
		        sizeof(size64_t), /* node alignment */
		        vmsz - (size64_t)nodeoff,
			dircnt + nondircnt);
	if (!ok) {
		return BOOL_FALSE;
	}

	/* extract the root ino and allocate a node for
	 * the root and for the orphanage. place both nodes
	 * in the hash list. make the orphanage a child of
	 * root, and indicate he is real.
	 *
	 * Note that we assume that the root inode always
	 * has a generation count of zero - which is true.
	 */
	persp->p_rootino = rootino;
	persp->p_rooth = Node_alloc(rootino,
				     0, /* gen cnt */
				     NRH_NULL,
				     DAH_NULL,
				     NF_ISDIR | NF_REAL);
	if (persp->p_rooth == NH_NULL)
		return BOOL_FALSE;
	link_in(persp->p_rooth);
	persp->p_orphh = Node_alloc(orphino,
				     0, /* gen cnt */
				     namreg_add(orphname, strlen(orphname)),
				     DAH_NULL,
				     NF_ISDIR | NF_REAL);
	if (persp->p_orphh == NH_NULL)
		return BOOL_FALSE;
	link_in(persp->p_orphh);
	adopt(persp->p_rooth, persp->p_orphh, NRH_NULL);

	/* record if we should attempt to restore original owner/group
	 */
	persp->p_ownerpr = ownerpr;

	/* record if this is a full dump. can skip link processing if so
	 */
	persp->p_fullpr = fullpr;

	/* record if truncated generation numbers are required
	 */
	if (dumpformat < GLOBAL_HDR_VERSION_3) {
		persp->p_truncategenpr = BOOL_TRUE;
		mlog(MLOG_NORMAL | MLOG_DEBUG | MLOG_TREE, _(
		      "dump format version %u used truncated inode generation numbers\n"),
			dumpformat);
	} else if (truncategenpr) {
		persp->p_truncategenpr = BOOL_TRUE;
		mlog(MLOG_NORMAL | MLOG_DEBUG | MLOG_TREE, _(
		      "forcing use of truncated inode generation numbers\n"));
	} else {
		persp->p_truncategenpr = BOOL_FALSE;
	}

	return BOOL_TRUE;
}

bool_t
tree_sync(char *hkdir,
	   char *dstdir,
	   bool_t toconlypr,
	   bool_t fullpr,
	   bool_t dstdirisxfspr)
{
	off64_t nodeoff;
	char *perspath;
	bool_t ok;
	int rval;

	if (persp) {
		return BOOL_TRUE;
	}

	/* sanity checks
	 */
	assert(!(PERSSZ % pgsz));
	assert(sizeof(persp) <= PERSSZ);
	assert(sizeof(node_t) <= NODESZ);
	assert(!persp);
	assert(!tranp);

	/* allocate transient state
	 */
	tranp = (tran_t *)calloc(1, sizeof(tran_t));
	assert(tranp);

	tranp->t_toconlypr = toconlypr;
	tranp->t_hkdir = hkdir;
	tranp->t_dstdir = dstdir;
	tranp->t_dstdirisxfspr = dstdirisxfspr;

	/* allocate a char string buffer to hold the abs. pathname
	 * of the orphanage directory file. load it with the pathname.
	 */
	tranp->t_orphdir = open_pathalloc(tranp->t_dstdir,
					   orphname,
					   0);

	/* determine if housekeeping dir is within the destination.
	 * generate a relative path containing the difference,
	 * else null. will not restore into there.
	 */
	if (strcmp(dstdir, ".")) {
		tranp->t_hksubtree = path_diff(hkdir, dstdir);
	} else {
		tranp->t_hksubtree = 0;
	}

	/* re-create the orphanage (in case someone rmdir'ed it)
	 */
	rval = mkdir(tranp->t_orphdir, S_IRWXU);
	if (rval && errno != EEXIST) {
		mlog(MLOG_NORMAL | MLOG_ERROR | MLOG_TREE, _(
		      "unable to recreate %s: %s\n"),
		      tranp->t_orphdir,
		      strerror(errno));
		return BOOL_FALSE;
	}

	/* build a full pathname to pers. state file
	 */
	perspath = open_pathalloc(tranp->t_hkdir, persname, 0);

	/* re-open the persistent state file
	 */
	tranp->t_persfd = open(perspath, O_RDWR);
	if (tranp->t_persfd < 0) {
		mlog(MLOG_NORMAL | MLOG_ERROR | MLOG_TREE, _(
		      "could not open %s: %s\n"),
		      perspath,
		      strerror(errno));
		return BOOL_FALSE;
	}

	/* mmap the persistent state
	 */
	assert(!(PERSSZ % pgsz));
	persp = (treepers_t *) mmap_autogrow(
				     PERSSZ,
				     tranp->t_persfd,
				     (off64_t)0);
	if (persp == (treepers_t *)-1) {
		mlog(MLOG_NORMAL | MLOG_ERROR | MLOG_TREE, _(
		      "unable to map %s: %s\n"),
		      perspath,
		      strerror(errno));
		return BOOL_FALSE;
	}

	/* update the fullpr field of the persistent state to match
	 * the input of our caller.
	 */
	persp->p_fullpr = fullpr;

	/* regardless of the format of this dump, if the previously applied
	 * dump used truncated generation numbers, then we need to as well.
	 */
	if (persp->p_truncategenpr) {
		mlog(MLOG_NORMAL | MLOG_DEBUG | MLOG_TREE, _(
		      "using truncated inode generation numbers for "
		      "compatibility with previously applied restore\n"));
	}

	/* rsynchronize with the hash abstraction. it will map more of the
	 * persistent state file.
	 */
	ok = hash_sync(perspath);
	if (!ok) {
		return BOOL_FALSE;
	}

	/* synchronize with the node abstraction.
	 */
	assert(persp->p_hashsz <= (size64_t)(OFF64MAX - (off64_t)PERSSZ));
	nodeoff = (off64_t)PERSSZ + (off64_t)persp->p_hashsz;
	ok = node_sync(tranp->t_persfd, nodeoff);
	if (!ok) {
		return BOOL_FALSE;
	}

	/* extract the root ino and allocate a node for
	 * the root and for the orphanage. place both nodes
	 * in the hash list. make the orphanage a child of
	 * root, and indicate he is real.
	 */
	return BOOL_TRUE;
}

bool_t
tree_check_dump_format(uint32_t dumpformat)
{
	if (dumpformat < GLOBAL_HDR_VERSION_3 && !persp->p_truncategenpr) {
		mlog(MLOG_NORMAL | MLOG_ERROR | MLOG_TREE, _(
		      "encountered dump format %d after a "
		      "restore of format %d or newer\n"),
			dumpformat, GLOBAL_HDR_VERSION_3);
		mlog(MLOG_NORMAL | MLOG_ERROR | MLOG_TREE, _(
		      "to restore this series of dumps, use the -%c "
		      "option on the first restore\n"),
			GETOPT_FMT2COMPAT);
		return BOOL_FALSE;
	}

	return BOOL_TRUE;
}

/* recursively descend the tree clearing REFED and DIRDUMPED and NEWORPH
 * flags. force the orphanage to be refed and dumped, so we won't try
 * to orphan it, and so things added to it won't look like they are
 * referenced during ref adj. also null dirattr handles, since they are
 * not retained across dump session restores.
 */
static void tree_marknoref_recurse(nh_t parh);

void
tree_marknoref(void)
{
	tree_marknoref_recurse(persp->p_rooth);

	{
		node_t *orphp;
		orphp = Node_map(persp->p_orphh);
		orphp->n_flags |= (NF_REFED | NF_DUMPEDDIR);
		assert(orphp->n_dah == DAH_NULL);
		Node_unmap(persp->p_orphh, &orphp);
	}
}

static void
tree_marknoref_recurse(nh_t parh)
{
	nh_t cldh;

	{
		node_t *parp;
		parp = Node_map(parh);
		parp->n_flags &= ~(NF_REFED | NF_DUMPEDDIR | NF_NEWORPH);

		parp->n_dah = DAH_NULL;
		cldh = parp->n_cldh;
		Node_unmap(parh, &parp);
	}
	while (cldh != NH_NULL) {
		node_t *cldp;
		nh_t nextcldh;
		tree_marknoref_recurse(cldh); /* RECURSION */
		cldp = Node_map(cldh);
		nextcldh = cldp->n_sibh;
		Node_unmap(cldh, &cldp);
		cldh = nextcldh;
	}
}

void
tree_markallsubtree(bool_t sensepr)
{
	if (!sensepr) {
		persp->p_ignoreorphpr = BOOL_TRUE;
	}
	selsubtree(persp->p_rooth, sensepr);
}

nh_t
tree_begindir(filehdr_t *fhdrp, dah_t *dahp)
{
	nh_t hardh;
	xfs_ino_t ino = fhdrp->fh_stat.bs_ino;
	gen_t gen = fhdrp->fh_stat.bs_gen;
	dah_t dah;

	if (persp->p_truncategenpr) {
		gen = BIGGEN2GEN(gen);
	}

	/* sanity check - orphino is supposed to be an unused ino!
	 */
	assert(ino != orphino);

	/* lookup head of hardlink list
	 */
	hardh = link_hardh(ino, gen);
	if (need_fixrootdir == BOOL_FALSE &&
	    !(ino != persp->p_rootino || hardh == persp->p_rooth)) {
		mlog(MLOG_ERROR | MLOG_TREE,
"%s:%d: %s: Assertion `ino != persp->p_rootino || hardh == persp->p_rooth` failed.\n",
			__FILE__, __LINE__, __func__);
		mlog(MLOG_ERROR | MLOG_TREE, _(
"False root detected. Recovery may be possible using the `-x` option\n"));
		return NH_NULL;
	}

	/* already present
	 */
	if (hardh != NH_NULL) {
		node_t *hardp;
		hardp = Node_map(hardh);
		if (!(hardp->n_flags & NF_ISDIR)) {
			/* case 1: previously seen as dirent, now know is dir
			 */
			mlog(MLOG_TRACE | MLOG_TREE,
			      "directory %llu %u (%u): "
			      "upgrading to dir\n",
			      ino,
			      gen,
			      fhdrp->fh_stat.bs_gen);
			if (!tranp->t_toconlypr) {
				assert(hardp->n_dah == DAH_NULL);
				hardp->n_dah = dirattr_add(fhdrp);
			}
		} else if (!tranp->t_toconlypr && hardp->n_dah == DAH_NULL) {
			/* case 2: node is a dir, but had thrown away dirattr
			 */
			mlog(MLOG_TRACE | MLOG_TREE,
			      "directory %llu %u (%u): "
			      "updating\n",
			      ino,
			      gen,
			      fhdrp->fh_stat.bs_gen);
			hardp->n_dah = dirattr_add(fhdrp);
		} else {
			/* case 3: already has dirattr; must be restart
			 */
			mlog(MLOG_TRACE | MLOG_TREE,
			      "directory %llu %u (%u): "
			      "retaining\n",
			      ino,
			      gen,
			      fhdrp->fh_stat.bs_gen);
		}
		hardp->n_flags |= NF_ISDIR;
		hardp->n_flags |= NF_DUMPEDDIR;
		*dahp = hardp->n_dah;
		Node_unmap(hardh, &hardp);
	} else {
		/* case 4: first time seen
		 */
		mlog(MLOG_TRACE | MLOG_TREE,
		      "directory %llu %u (%u): "
		      "new\n",
		      ino,
		      gen,
		      fhdrp->fh_stat.bs_gen);
		if (!tranp->t_toconlypr) {
			dah = dirattr_add(fhdrp);
		} else {
			dah = DAH_NULL;
		}
		hardh = Node_alloc(ino,
				    gen,
				    NRH_NULL,
				    dah,
				    NF_ISDIR | NF_NEWORPH);
		if (hardh == NH_NULL)
			return NH_NULL;
		link_in(hardh);
		adopt(persp->p_orphh, hardh, NRH_NULL);
		*dahp = dah;
	}
	return hardh;
}

rv_t
tree_addent(nh_t parh, xfs_ino_t ino, gen_t gen, char *name, size_t namelen)
{
	nh_t hardh;

	if (persp->p_truncategenpr) {
		gen = BIGGEN2GEN(gen);
	}

	/* sanity check - orphino is supposed to be an unused ino!
	 */
	assert(ino != orphino);

	/* don't allow entries named "orphanage" under root to be added
	 */
	if (parh == persp->p_rooth && !strcmp(name, orphname)) {
		mlog(MLOG_DEBUG | MLOG_TREE,
		      "dirent %s %llu %u: "
		      "skipping (reserved)\n",
		      name,
		      ino,
		      gen);
		return RV_OK;
	}

	/* if the parent is null, just ignore
	 */
	if (parh == NH_NULL) {
		mlog(MLOG_DEBUG | MLOG_TREE,
		      "dirent %s %llu %u: "
		      "skipping (null parent)\n",
		      name,
		      ino,
		      gen);
		return RV_OK;
	}

	/* see if one or more links to this ino already exist.
	 */
	hardh = link_hardh(ino, gen);

	if (hardh != NH_NULL) {
		node_t *hardp;
		hardp = Node_map(hardh);
		if (hardp->n_flags & NF_ISDIR) {
			nh_t renameh;
			node_t *renamep;
			/* REFERENCED */
			int namebuflen;

			hardp->n_flags |= NF_REFED;
			if (hardp->n_parh == persp->p_orphh) {
				/* dir now seen as entry
				 * if in orph but REAL, must be pending rename
				 */
				if ((hardp->n_flags & NF_REAL)
				     &&
				     hardp->n_lnkh == NH_NULL) {
					hardp->n_lnkh = Node_alloc(ino,
								    gen,
								    NRH_NULL,
								    DAH_NULL,
								    0);
					if (hardp->n_lnkh == NH_NULL)
						return RV_ERROR;
				}
				if (hardp->n_lnkh != NH_NULL) {
					assert(hardp->n_flags & NF_REAL);
					renameh = hardp->n_lnkh;
					renamep = Node_map(renameh);
					if (renamep->n_parh == NH_NULL) {
						mlog(MLOG_DEBUG | MLOG_TREE,
						      "dirent %s %llu %u: "
						      "adopting (dir par)\n",
						      name,
						      ino,
						      gen);
						renamep->n_parh = parh;
					}
					if (renamep->n_parh != parh) {
						mlog(MLOG_DEBUG | MLOG_TREE,
						      "dirent %s %llu %u: "
						      "re-adopting (dir par)\n",
						      name,
						      ino,
						      gen);
						renamep->n_parh = parh;
					}
					if (renamep->n_nrh != NRH_NULL) {
						namebuflen
						=
						namreg_get(renamep->n_nrh,
							    tranp->t_namebuf,
						    sizeof(tranp->t_namebuf));
						assert(namebuflen > 0);
						if (strcmp(name,
							   tranp->t_namebuf)) {
							mlog(MLOG_DEBUG
							      |
							      MLOG_TREE,
							      "dirent %s "
							      "%llu %u: "
							      "re-adopting "
							      "(dir name): "
							      "was %s\n",
							      name,
							      ino,
							      gen,
							     tranp->t_namebuf);
							namreg_del(
							       renamep->n_nrh);
							renamep->n_nrh =
								       NRH_NULL;
						}
					}
					if (renamep->n_nrh == NRH_NULL) {
						renamep->n_nrh
						=
						namreg_add(name, namelen);
						renamep->n_parh = parh;
					}
					Node_unmap(renameh, &renamep);
				} else {
					nrh_t nrh;

					hardp->n_flags &= ~NF_NEWORPH;
					assert(hardp->n_nrh == NRH_NULL);
					assert(hardp->n_parh != NH_NULL);
					nrh = disown(hardh);
					assert(nrh == NRH_NULL);
					nrh = namreg_add(name, namelen);
					adopt(parh, hardh, nrh);
					mlog(MLOG_DEBUG | MLOG_TREE,
					      "dirent %s %llu %u: "
					      "updating (dir)\n",
					      name,
					      ino,
					      gen);
				}
			} else {
				assert(hardp->n_nrh != NRH_NULL);

				namebuflen
				=
				namreg_get(hardp->n_nrh,
					    tranp->t_namebuf,
					    sizeof(tranp->t_namebuf));
				assert(namebuflen > 0);
				if (hardp->n_parh == parh
				     &&
				     !strcmp(tranp->t_namebuf, name)) {
					/* dir seen as entry again
					 */
					if (hardp->n_lnkh != NH_NULL) {
						/* rescind rename
						 */
						mlog(MLOG_DEBUG | MLOG_TREE,
						      "dirent %s %llu %u: "
						      "rescind rename (dir)\n",
						      name,
						      ino,
						      gen);
						Node_free(&hardp->n_lnkh);
					} else {
						mlog(MLOG_DEBUG | MLOG_TREE,
						      "dirent %s %llu %u: "
						      "retaining (dir)\n",
						      name,
						      ino,
						      gen);
					}
				} else {
					/* dir rename
					 */
					nh_t renameh;
					node_t *renamep;
					if (hardp->n_lnkh == NH_NULL) {
						renameh = Node_alloc(ino,
								      gen,
								      NRH_NULL,
								      DAH_NULL,
								      0);
						if (renameh == NH_NULL)
							return RV_ERROR;
						hardp->n_lnkh = renameh;
					} else {
						mlog(MLOG_DEBUG | MLOG_TREE,
						      "dirent %s %llu %u: "
						      "re-renaming\n",
						      name,
						      ino,
						      gen);
						renameh = hardp->n_lnkh;
						renamep = Node_map(renameh);
						renamep->n_parh = NH_NULL;
						if (renamep->n_nrh
						     !=
						     NRH_NULL) {
						   namreg_del(renamep->n_nrh);
						}
						renamep->n_nrh = NRH_NULL;
						Node_unmap(renameh, &renamep);
					}
					renamep = Node_map(renameh);
					assert(hardp->n_parh != NH_NULL);
					if (hardp->n_parh != parh) {
						/* different parent
						 */
						renamep->n_parh = parh;
						mlog(MLOG_DEBUG | MLOG_TREE,
						      "dirent %s %llu %u: "
						      "renaming (parent)\n",
						      name,
						      ino,
						      gen);
					}
					if (strcmp(tranp->t_namebuf, name)) {
						/* different name
						 */
						renamep->n_nrh =
						    namreg_add(name, namelen);
						mlog(MLOG_DEBUG | MLOG_TREE,
						      "dirent %s %llu %u: "
						      "renaming (name)\n",
						      name,
						      ino,
						      gen);
					}
					Node_unmap(renameh, &renamep);
				}
			}
		} else {
			nh_t matchh;
			matchh = link_matchh(hardh, parh, name);
			if (matchh != NH_NULL) {
				/* entry already exists
				 */
				node_t *matchp;
				matchp = Node_map(matchh);
				matchp->n_flags |= NF_REFED;
				Node_unmap(matchh, &matchp);
				mlog(MLOG_DEBUG | MLOG_TREE,
				      "dirent %s %llu %u: "
				      "retaining (nondir)\n",
				      name,
				      ino,
				      gen);
			} else {
				/* case 5: new hard link
				 */
				nrh_t nrh;
				nh_t linkh;
				nrh = namreg_add(name, namelen);
				linkh = Node_alloc(ino,
						    gen,
						    NRH_NULL,
						    DAH_NULL,
						    NF_REFED);
				if (linkh == NH_NULL)
					return RV_ERROR;
				link_in(linkh);
				adopt(parh, linkh, nrh);
				mlog(MLOG_DEBUG | MLOG_TREE,
				      "dirent %s %llu %u: "
				      "adding (link)\n",
				      name,
				      ino,
				      gen);
			}
		}
		Node_unmap(hardh, &hardp);
	} else {
		/* case 6: new entry
		 */
		nrh_t nrh;
		nrh = namreg_add(name, namelen);
		hardh = Node_alloc(ino,
				    gen,
				    NRH_NULL,
				    DAH_NULL,
				    NF_REFED);
		if (hardh == NH_NULL)
			return RV_ERROR;
		link_in(hardh);
		adopt(parh, hardh, nrh);
		mlog(MLOG_DEBUG | MLOG_TREE,
		      "dirent %s %llu %u: "
		      "adding (new)\n",
		      name,
		      ino,
		      gen);
	}
	/* found the fake rootino from subdir, need fix p_rooth. */
	if (need_fixrootdir == BOOL_TRUE &&
	    persp->p_rootino == ino && hardh != persp->p_rooth) {
		mlog(MLOG_NORMAL,
		     _("found fake rootino #%llu, will fix.\n"), ino);
		persp->p_rooth = hardh;
	}
	return RV_OK;
}

/* ARGSUSED */
void
tree_enddir(nh_t dirh)
{
}

bool_t
tree_subtree_parse(bool_t sensepr, char *path)
{
	nh_t namedh;
	nh_t parh;
	nh_t cldh;
	xfs_ino_t ino;
	bool_t isdirpr;
	bool_t isselpr;
	bool_t ok;

	/* walk the tree down this relative pathname from the root.
	 */
	ok = tsi_walkpath(path,
			   NH_NULL,
			   persp->p_rooth,
			   0,
			   0,
			   &namedh,
			   &parh,
			   &cldh,
			   &ino,
			   &isdirpr,
			   &isselpr);
	if (!ok) {
		return BOOL_FALSE;
	}

	/* set or clear flag in this node and all of its children,
	 * and ajust parentage flags.
	 */
	selsubtree(namedh, sensepr);

	return BOOL_TRUE;
}

/* tree_post - called after the dirdump has been applied.
 * first phase is to eliminate all unreferenced dirents.
 * done by recursive depth-wise descent of the tree. on the way
 * up, unlink or orphan unreferenced nondirs, unlink unreferenced
 * dirs, orphan dirs to be renamed. if a dir is unreferenced, but
 * contains referenced dirents, orphan those dirents. orphan unreferenced
 * nondirs if they are the last link to an inode referenced but not real
 * somewhere else in the tree. next, make new directories. then rename
 * directories. finally, create hardlinks from orphanage.
 */
static bool_t noref_elim_recurse(nh_t parh,
				  nh_t cldh,
				  bool_t parrefpr,
				  char *path1,
				  char *path2);

static bool_t mkdirs_recurse(nh_t parh,
			      nh_t cldh,
			      char *path);

static bool_t rename_dirs(nh_t cldh,
			   char *path1,
			   char *path2);

static bool_t proc_hardlinks(char *path1,
			      char *path2);

bool_t
tree_post(char *path1, char *path2)
{
	node_t *rootp;
	node_t *orphp;
	nh_t cldh;
	bool_t ok;

	/* eliminate unreferenced dirents
	 */
	if (!persp->p_fullpr) {
		mlog(MLOG_DEBUG | MLOG_TREE,
		      "eliminating unreferenced directory entries\n");
		rootp = Node_map(persp->p_rooth);
		cldh = rootp->n_cldh;
		Node_unmap(persp->p_rooth, &rootp);
		ok = noref_elim_recurse(persp->p_rooth,
					 cldh,
					 BOOL_TRUE,
					 path1,
					 path2);
		if (!ok) {
			return BOOL_FALSE;
		}
	}

	/* make new directories
	 */
	mlog(MLOG_DEBUG | MLOG_TREE,
	      "making new directories\n");
	rootp = Node_map(persp->p_rooth);
	cldh = rootp->n_cldh;
	Node_unmap(persp->p_rooth, &rootp);
	ok = mkdirs_recurse(persp->p_rooth, cldh, path1);
	if (!ok) {
		return BOOL_FALSE;
	}

#ifdef TREE_CHK
	assert(tree_chk());
#endif /* TREE_CHK */

	/* rename directories
	 */
	if (!persp->p_fullpr) {
		mlog(MLOG_DEBUG | MLOG_TREE,
		      "performing directory renames\n");
		orphp = Node_map(persp->p_orphh);
		cldh = orphp->n_cldh;
		Node_unmap(persp->p_orphh, &orphp);
		ok = rename_dirs(cldh, path1, path2);
		if (!ok) {
			return BOOL_FALSE;
		}
	}

#ifdef TREE_CHK
	assert(tree_chk());
#endif /* TREE_CHK */

	/* process hard links
	 */
	if (!persp->p_fullpr) {
		mlog(MLOG_DEBUG | MLOG_TREE,
		      "processing hard links\n");
		ok = proc_hardlinks(path1, path2);
		if (!ok) {
			return BOOL_FALSE;
		}
	}

	return BOOL_TRUE;
}

/* ARGSUSED */
static bool_t
noref_elim_recurse(nh_t parh,
		    nh_t cldh,
		    bool_t parrefpr,
		    char *path1,
		    char *path2)
{
	while (cldh != NH_NULL) {
		node_t *cldp;
		xfs_ino_t ino;
		gen_t gen;
		bool_t inorphanagepr;
		bool_t isdirpr;
		bool_t isselpr;
		bool_t isrealpr;
		bool_t isrefpr;
		bool_t isrenamepr;
		nh_t renameh;
		nh_t grandcldh;
		nh_t nextcldh;
		int rval;
		bool_t ok;

		cldp = Node_map(cldh);
		ino = cldp->n_ino;
		gen = cldp->n_gen;
		inorphanagepr = cldp->n_parh == persp->p_orphh;
		isdirpr = (cldp->n_flags & NF_ISDIR);
		isrealpr = (cldp->n_flags & NF_REAL);
		isrefpr = (cldp->n_flags & NF_REFED);
		isselpr = (cldp->n_flags & NF_SUBTREE);
		isrenamepr = (isdirpr && cldp->n_lnkh != NH_NULL);
		renameh = cldp->n_lnkh;
		grandcldh = cldp->n_cldh;
		nextcldh = cldp->n_sibh;

#ifdef TREE_DEBUG
		ok = Node2path(cldh, path1, _("noref debug"));
		mlog(MLOG_TRACE | MLOG_TREE,
		      "noref: %s: isdir = %d, isreal = %d, isref = %d, "
		      "isrenamedir = %d\n",
		      path1, isdirpr, isrealpr, isrefpr, isrenamepr);
#endif

		Node_unmap(cldh, &cldp);

		if (!isselpr) {
			cldh = nextcldh;
			continue;
		}

		if (isdirpr) {
			bool_t ok;

			ok = noref_elim_recurse(cldh,
						 grandcldh,
						 isrefpr,
						 path1,
						 path2);
						/* RECURSION */
			if (!ok) {
				return BOOL_FALSE;
			}

			if (inorphanagepr) {
				cldh = nextcldh;
				continue;
			}

			if (!isrefpr) {
				nrh_t nrh;

				assert(!isrenamepr);
				if (isrealpr) {
					ok = Node2path(cldh, path1, _("rmdir"));
					if (!ok) {
						cldh = nextcldh;
						continue;
					}
					mlog(MLOG_TRACE | MLOG_TREE,
					      "rmdir %s\n",
					      path1);
					rval = rmdir(path1);
					if (rval) {
						mlog(MLOG_NORMAL
						      |
						      MLOG_WARNING
						      |
						      MLOG_TREE, _(
						    "unable to rmdir %s: %s\n"),
						     path1,
						     strerror(errno));
						cldh = nextcldh;
						continue;
					}
				}
				nrh = disown(cldh);
				assert(nrh != NRH_NULL);
				namreg_del(nrh);
				link_out(cldh);
				Node_free(&cldh);
			}

			if (isrenamepr) {
				nrh_t nrh;
				node_t *renamep;

				assert(isrefpr);
				assert(isrealpr);
				ok = Node2path(cldh,
						path1,
						_("tmp dir rename src"));
				if (!ok) {
					cldh = nextcldh;
					continue;
				}
				nrh = disown(cldh);
				assert(nrh != NRH_NULL);
				adopt(persp->p_orphh, cldh, NRH_NULL);
				ok = Node2path(cldh,
						path2,
						_("tmp dir rename dst"));
				if (!ok) {
					/* REFERENCED */
					nrh_t dummynrh;
					dummynrh = disown(cldh);
					assert(dummynrh == NRH_NULL);
					adopt(parh, cldh, nrh);
					cldh = nextcldh;
					continue;
				}
				mlog(MLOG_TRACE | MLOG_TREE,
				      "rename dir %s to %s\n",
				      path1,
				      path2);
				rval = rename(path1, path2);
				if (rval) {
					/* REFERENCED */
					nrh_t dummynrh;
					mlog(MLOG_NORMAL | MLOG_WARNING, _(
					      "unable to rename dir %s "
					      "to dir %s: %s\n"),
					      path1,
					      path2,
					      strerror(errno));
					dummynrh = disown(cldh);
					assert(dummynrh == NRH_NULL);
					adopt(parh, cldh, nrh);
					cldh = nextcldh;
					continue;
				}
				cldp = Node_map(cldh);
				renamep = Node_map(renameh);
				if (renamep->n_nrh == NRH_NULL) {
					renamep->n_nrh = nrh;
				} else {
					namreg_del(nrh);
				}
				if (renamep->n_parh == NH_NULL) {
					renamep->n_parh = parh;
				}
				cldp->n_flags |= NF_NEWORPH;
				Node_unmap(renameh, &renamep);
				Node_unmap(cldh, &cldp);
			}
		} else  {
			/* determine if we can unlink this node.
			 * if its not real, and not refed, simple.
			 * if real and not refed and there is at least
			 * one unreal refed node and no other real
			 * nodes around, must put this one in orphanage
			 * rather than unlinking it.
			 */
			bool_t mustorphpr;
			bool_t canunlinkpr;

			if (inorphanagepr) {
				cldh = nextcldh;
				continue;
			}

			mustorphpr = BOOL_FALSE;
			canunlinkpr = !isrefpr && !isrealpr;
			if (!isrefpr && isrealpr) {
				nh_t hardh;
				bool_t neededpr;
				hardh = link_hardh(ino, gen);
				assert(hardh != NH_NULL);
				canunlinkpr = BOOL_FALSE;
				neededpr = BOOL_FALSE;
				/* tes@sgi.com:
				 * This loop seems to assume that
				 * REAL files come before NON-REALs
				 * so that we will break out first
				 * if we find a REAL file.
				 */
				while (hardh != NH_NULL) {
					node_t *hardp;
					bool_t hardisrefpr;
					bool_t hardisrealpr;
					nh_t nexthardh;

					hardp = Node_map(hardh);
					hardisrefpr = hardp->n_flags & NF_REFED;
					hardisrealpr = hardp->n_flags & NF_REAL;
					nexthardh = hardp->n_lnkh;
					Node_unmap(hardh, &hardp);
					if (hardh != cldh && hardisrealpr) {
						break;
					}
					if (hardisrefpr && !hardisrealpr) {
						neededpr = BOOL_TRUE;
					}
					hardh = nexthardh;
				}
				if (neededpr) {
					mustorphpr = BOOL_TRUE;
				}
				else {
					canunlinkpr = BOOL_TRUE;
				}
			}

			if (mustorphpr) {
				/* rename file to orphanage */
				nrh_t nrh;
				assert(!canunlinkpr);
				ok = Node2path(cldh,
						path1,
						_("tmp nondir rename src"));
				if (!ok) {
					cldh = nextcldh;
					continue;
				}
				nrh = disown(cldh);
				assert(nrh != NRH_NULL);
				adopt(persp->p_orphh, cldh, NRH_NULL);
				ok = Node2path(cldh,
						path2,
						_("tmp nondir rename dst"));
				if (!ok) {
					/* REFERENCED */
					nrh_t dummynrh;
					dummynrh = disown(cldh);
					assert(dummynrh == NRH_NULL);
					adopt(parh, cldh, nrh);
					cldh = nextcldh;
					continue;
				}
				mlog(MLOG_TRACE | MLOG_TREE,
				      "rename nondir %s to %s\n",
				      path1,
				      path2);
				rval = rename(path1, path2);
				if (rval) {
					/* REFERENCED */
					nrh_t dummynrh;
					mlog(MLOG_NORMAL | MLOG_WARNING, _(
					      "unable to rename nondir %s "
					      "to nondir %s: %s\n"),
					      path1,
					      path2,
					      strerror(errno));
					dummynrh = disown(cldh);
					assert(dummynrh == NRH_NULL);
					adopt(parh, cldh, nrh);
					cldh = nextcldh;
					continue;
				}
				namreg_del(nrh);
				cldp = Node_map(cldh);
				cldp->n_flags |= NF_NEWORPH;
				Node_unmap(cldh, &cldp);
			}
			if (canunlinkpr) {
				/* REFERENCED */
				nrh_t nrh;

				assert(!mustorphpr);
				if (isrealpr) {
					ok = Node2path(cldh, path1, _("rmdir"));
					if (!ok) {
						cldh = nextcldh;
						continue;
					}
					mlog(MLOG_TRACE | MLOG_TREE,
					      "unlink %s\n",
					      path1);
					rval = unlink(path1);
					if (rval) {
						mlog(MLOG_NORMAL
						      |
						      MLOG_WARNING
						      |
						      MLOG_TREE, _(
						     "unable to unlink nondir "
						     "%s: %s\n"),
						     path1,
						     strerror(errno));
						cldh = nextcldh;
						continue;
					}
				}
				nrh = disown(cldh);
				assert(nrh != NRH_NULL);
				link_out(cldh);
				Node_free(&cldh);
			}
		}

		/* next!
		 */
		cldh = nextcldh;
	}

	return BOOL_TRUE;
}

/* ARGSUSED */
static bool_t
mkdirs_recurse(nh_t parh, nh_t cldh, char *path)
{
	while (cldh != NH_NULL) {
		node_t *cldp;
		bool_t isdirpr;
		bool_t isselpr;
		bool_t isrealpr;
		bool_t isrefpr;
		nh_t grandcldh;
		nh_t nextcldh;

		cldp = Node_map(cldh);
		isdirpr = (cldp->n_flags & NF_ISDIR);
		isrealpr = (cldp->n_flags & NF_REAL);
		isrefpr = (cldp->n_flags & NF_REFED);
		isselpr = (cldp->n_flags & NF_SUBTREE);
		grandcldh = cldp->n_cldh;
		nextcldh = cldp->n_sibh;
		Node_unmap(cldh, &cldp);

		/* if needed, create a directory and update real flag
		 */
		if (isdirpr && !isrealpr && isrefpr && isselpr) {
			int rval;

			if (!Node2path(cldh, path, _("makedir"))) {
				cldh = nextcldh;
				continue;
			}
			if (tranp->t_toconlypr) {
				rval = 0;
			} else {
				mlog(MLOG_TRACE | MLOG_TREE,
				      "mkdir %s\n",
				      path);
				rval = mkdir(path, S_IRWXU);
			}
			if (rval && errno != EEXIST) {
				mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_TREE, _(
				      "mkdir %s failed: %s\n"),
				      path,
				      strerror(errno));

			} else {
				cldp = Node_map(cldh);
				cldp->n_flags |= NF_REAL;
				Node_unmap(cldh, &cldp);
				isrealpr = BOOL_TRUE;
			}
		}

		/* if a real selected directory, recurse
		 */
		if (isdirpr && isrealpr && isselpr) {
			bool_t ok;
			ok = mkdirs_recurse(cldh, grandcldh, path);
							/* RECURSION */
			if (!ok) {
				return BOOL_FALSE;
			}
		}

		/* next!
		 */
		cldh = nextcldh;
	}

	return BOOL_TRUE;
}

static bool_t
rename_dirs(nh_t cldh,
	     char *path1,
	     char *path2)
{
	while (cldh != NH_NULL) {
		node_t *cldp;
		/* REFERENCED */
		nh_t parh;
		bool_t isdirpr;
		nh_t renameh;
		bool_t isrenamepr;
		nh_t nextcldh;
		nh_t newparh;
		nh_t newnrh;

		cldp = Node_map(cldh);
		parh = cldp->n_parh;
		isdirpr = cldp->n_flags & NF_ISDIR;
		renameh = cldp->n_lnkh;
		isrenamepr = isdirpr && renameh != NH_NULL;
		nextcldh = cldp->n_sibh;
		Node_unmap(cldh, &cldp);
		assert(parh == persp->p_orphh);

		if (isrenamepr) {
			node_t *renamep;
			int rval;
			/* REFERENCED */
			nrh_t dummynrh;
			bool_t ok;

			renamep = Node_map(renameh);
			newparh = renamep->n_parh;
			newnrh = renamep->n_nrh;
			Node_unmap(renameh, &renamep);
			ok = Node2path(cldh, path1, _("rename dir"));
			if (!ok) {
				cldh = nextcldh;
				continue;
			}
			dummynrh = disown(cldh);
			assert(dummynrh == NRH_NULL);
			adopt(newparh, cldh, newnrh);
			ok = Node2path(cldh, path2, _("rename dir"));
			if (!ok) {
				dummynrh = disown(cldh);
				assert(dummynrh == newnrh);
				adopt(persp->p_orphh, cldh, NRH_NULL);
				cldp = Node_map(cldh);
				cldp->n_nrh = NRH_NULL;
				Node_unmap(cldh, &cldp);
				cldh = nextcldh;
				continue;
			}
			mlog(MLOG_TRACE | MLOG_TREE,
			      "rename dir %s to %s\n",
			      path1,
			      path2);
			rval = rename(path1, path2);
			if (rval) {
				mlog(MLOG_NORMAL | MLOG_WARNING, _(
				      "unable to rename dir %s "
				      "to dir %s: %s\n"),
				      path1,
				      path2,
				      strerror(errno));
				dummynrh = disown(cldh);
				assert(dummynrh == newnrh);
				adopt(persp->p_orphh, cldh, NRH_NULL);
				cldh = nextcldh;
				continue;
			}
			cldp = Node_map(cldh);
			cldp->n_flags &= ~NF_NEWORPH;
			cldp->n_lnkh = NH_NULL;
			Node_unmap(cldh, &cldp);
			Node_free(&renameh);
		}

		/* next!
		 */
		cldh = nextcldh;
	}

	return BOOL_TRUE;
}

/* will call funcp for all links to be created. will abort if funcp
 * ever returns FALSE;
 */
rv_t
tree_cb_links(xfs_ino_t ino,
	       gen_t gen,
	       int32_t ctime,
	       int32_t mtime,
	       bool_t (*funcp)(void *contextp,
				   bool_t linkpr,
				   char *path1,
				   char *path2),
	       void *contextp,
	       char *path1,
	       char *path2)
{
	nh_t hardh;
	nh_t nh;
	char *path;
	bool_t ok;
	int  rval;

	if (persp->p_truncategenpr) {
		gen = BIGGEN2GEN(gen);
	}

	/* find the hardhead
	 */
	hardh = link_hardh(ino, gen);

	/* loop through all hard links, attempting to restore/link
	 */
	path = path1;
	for (nh = hardh; nh != NH_NULL; nh = link_nexth(nh)) {
		node_t *np;
		u_char_t flags;
		char *reasonstr;

		/* get the node flags
		 */
		np = Node_map(nh);
		flags = np->n_flags;
		Node_unmap(nh, &np);

		/* build a pathname
		 */
		ok = Node2path(nh, path, _("restore"));
		if (!ok) {
			continue;
		}

		/* skip if not in selected subtree
		 */
		if (!(flags & NF_SUBTREE)) {
			mlog((MLOG_NITTY + 1) | MLOG_TREE,
			      "skipping %s (ino %llu gen %u): %s\n",
			      path,
			      ino,
			      gen,
			      "not in selected subtree");
			continue;
		}

		/* don't restore into the housekeeping directory
		 */
		if (path_beginswith(path, tranp->t_hkdir)) {
			mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_TREE, _(
			      "cannot restore %s (ino %llu gen %u): "
			      "pathname contains %s\n"),
			      path,
			      ino,
			      gen,
			      tranp->t_hkdir);
			continue;
		}

		/* check if ok to overwrite: don't check if we've already
		 * been here and decided overwrite ok. if ok, set flag
		 * so we won't check again. in fact, can't check again
		 * since restore changes the answer.
		 */
		if (!(flags & NF_WRITTEN)) {
			bool_t exists;
			if (!content_overwrite_ok(path,
						     ctime,
						     mtime,
						     &reasonstr,
						     &exists)) {
				mlog(MLOG_TRACE | MLOG_TREE,
				      "skipping %s (ino %llu gen %u): %s\n",
				      path,
				      ino,
				      gen,
				      reasonstr);
				continue;
			} else {
				np = Node_map(nh);
				np->n_flags |= NF_WRITTEN;
				Node_unmap(nh, &np);

				/*
				 * We're the first stream to restore this file.
				 * Unlink it first to remove extended attributes
				 * that may have been set since the dump was
				 * taken.
				 */
				if (!tranp->t_toconlypr && exists) {
					rval = unlink(path);
					if (rval && errno != ENOENT) {
						mlog(MLOG_NORMAL |
						      MLOG_WARNING, _(
						      "unable to unlink "
						      "current file prior to "
						      "restore: "
						      "%s: %s: discarding\n"),
						      path,
						      strerror(errno));
						continue;
					}
				}
			}
		}

		/* call callback to restore file / create hard link.
		 * returns !ok if should abort.
		 */
		if (path == path2) {
			mlog(MLOG_TRACE | MLOG_TREE,
			      "link %s to %s (%llu %u)\n",
			      path1,
			      path2,
			      ino,
			      gen);
		} else {
			mlog(MLOG_TRACE | MLOG_TREE,
			      "restoring %s (%llu %u)\n",
			      path1,
			      ino,
			      gen);
		}
		ok = (*funcp)(contextp, path == path2, path1, path2);
		if (!ok) {
			return RV_NOTOK;
		}

		/* set flag, indicating node is now real
		 */
		np = Node_map(nh);
		np->n_flags |= NF_REAL;
		Node_unmap(nh, &np);

		/* switch to second path buffer, for link paths
		 */
		path = path2;
	}


	/* if not yet peeled from tape, do so: place in orphanage if
	 * no references found (i.e., hard link list empty).
	 */
	if (path == path1) {
		if (hardh != NH_NULL || persp->p_ignoreorphpr) {
			/*
			 * The file is referenced so the path is
			 * valid.  This means that if path begins
			 * with 'orphanage' the file is one of two
			 * things:
			 * 1) It's a file that really is an
			 * orphanage file from a previous restore
			 * that has now ended up on this dump tape.
			 * We don't really want to restore this file
			 * but, it's harmless to do so, it should
			 * happen rarely, and the path name is
			 * indistinguishable from ...
			 * 2) A file whose name was never resolved
			 * from root because of file corruption.
			 * Some granparent dir (parent dir of it's
			 * parent dir) was corrupted so the code that
			 * walks the trees was thus never able to set
			 * the NF_SUBTREE flag.  It then ends up here
			 * with a non-resolved name but a valid
			 * hard reference.  We really need to give
			 * the admin the opportunity to get this
			 * data since one corrupted dirnode would
			 * make the whole tree of data
			 * unreachable.  pv698761
			 */
			if (persp->p_ignoreorphpr || (strncmp(ORPH_NAME, path,
					    strlen(ORPH_NAME)) != 0)) {
				mlog(MLOG_DEBUG | MLOG_TREE,
				      "discarding %llu %u\n",
				      ino,
				      gen);
				ok = (*funcp)(contextp, BOOL_FALSE, 0, 0);
				if (!ok) {
					return RV_NOTOK;
				}
			} else {

				if (!tranp->t_toconlypr) {
					char *dir;
					char tmp[PATH_MAX];

					strcpy(tmp, path);
					dir = dirname(tmp);
					mkdir_r(dir);
				}

				mlog (MLOG_VERBOSE | MLOG_NOTE | MLOG_TREE, _(
				      "ino %llu salvaging file,"
				      " placing in %s\n"), ino, path1);
				ok = (*funcp)(contextp, path == path2,
					path1, path2);
				if (!ok) {
					return RV_NOTOK;
				}
			}
		} else {
			mlog(MLOG_VERBOSE | MLOG_NOTE | MLOG_TREE, _(
			      "ino %llu gen %u not referenced: "
			      "placing in orphanage\n"),
			      ino,
			      gen);
			nh = Node_alloc(ino,
					 gen,
					 NRH_NULL,
					 DAH_NULL,
					 NF_REAL | NF_NEWORPH);
			if (nh == NH_NULL) {
				mlog(MLOG_ERROR | MLOG_TREE, _(
				"node allocation failed when placing ino %llu"
				" in orphanage\n"), ino);
				return RV_ERROR; /* allocation failed */
			}
			link_in(nh);
			adopt(persp->p_orphh, nh, NRH_NULL);
			ok = Node2path(nh, path1, _("orphan"));
			assert(ok);
			(void)(*funcp)(contextp, BOOL_FALSE, path1, path2);
		}
	}
	return RV_OK;
}

/* uses flags cleared during directory restore (NF_DUMPEDDIR and NF_REFED)
 * to determine what directory entries are no longer needed. this can
 * be done because whenever a directory chenges, it and all of its current
 * entries are dumped. so if an entry is dumped which is a dir, but that
 * dir is not dumped, all of that directories entries are needed and so must
 * have their REFED flag cleared.
 *
 * does a recursive depth-wise descent of the tree, following this rule to
 * clear the REFED flags.
 */

static void tree_adjref_recurse(nh_t cldh,
				 bool_t pardumpedpr,
				 bool_t parrefedpr);

bool_t
tree_adjref(void)
{
	tree_adjref_recurse(persp->p_rooth, BOOL_FALSE, BOOL_TRUE);
	return BOOL_TRUE;
}

static void
tree_adjref_recurse(nh_t cldh,
		       bool_t pardumpedpr,
		       bool_t parrefedpr)
{
	nh_t grandcldh;
	bool_t clddumpedpr;
	bool_t cldrefedpr;
	{
		node_t *cldp;
		cldp = Node_map(cldh);
		if (!pardumpedpr && parrefedpr) {
			cldp->n_flags |= NF_REFED;
		}
		clddumpedpr = (int)cldp->n_flags & NF_DUMPEDDIR;
		cldrefedpr = (int)cldp->n_flags & NF_REFED;
		grandcldh = cldp->n_cldh;
		Node_unmap(cldh, &cldp);
	}
	while (grandcldh != NH_NULL) {
		node_t *grandcldp;
		nh_t nextgrandcldh;
		tree_adjref_recurse(grandcldh, clddumpedpr, cldrefedpr);
								/* RECURSION */
		grandcldp = Node_map(grandcldh);
		nextgrandcldh = grandcldp->n_sibh;
		Node_unmap(grandcldh, &grandcldp);
		grandcldh = nextgrandcldh;
	}
}

static bool_t tree_extattr_recurse(nh_t parh,
				    nh_t cldh,
				    bool_t (*cbfunc)(char *path,
							 dah_t dah),
				    char *path);

bool_t
tree_extattr(bool_t (*cbfunc)(char *path, dah_t dah), char *path)
{
	node_t *rootp;
	nh_t cldh;
	bool_t ok;

	rootp = Node_map(persp->p_rooth);
	cldh = rootp->n_cldh;
	Node_unmap(persp->p_rooth, &rootp);
	ok = tree_extattr_recurse(persp->p_rooth, cldh, cbfunc, path);

	return ok;
}

static bool_t
tree_extattr_recurse(nh_t parh,
		      nh_t cldh,
		      bool_t (*cbfunc)(char *path, dah_t dah),
		      char *path)
{
	node_t *parp;
	dah_t dah;
	bool_t ok;

	/* first update all children
	 */
	while (cldh != NH_NULL) {
		node_t *cldp;
		bool_t isdirpr;
		bool_t isselpr;
		bool_t isrealpr;
		nh_t grandcldh;
		nh_t nextcldh;

		cldp = Node_map(cldh);
		isdirpr = (cldp->n_flags & NF_ISDIR);
		isrealpr = (cldp->n_flags & NF_REAL);
		isselpr = (cldp->n_flags & NF_SUBTREE);
		grandcldh = cldp->n_cldh;
		nextcldh = cldp->n_sibh;
		Node_unmap(cldh, &cldp);

		/* if a real selected directory, recurse
		 */
		if (isdirpr && isrealpr && isselpr) {
			bool_t ok;
			ok = tree_extattr_recurse(cldh,
						   grandcldh,
						   cbfunc,
						   path);
							/* RECURSION */
			if (!ok) {
				return BOOL_FALSE;
			}
		}

		/* next!
		 */
		cldh = nextcldh;
	}

	/* now update self
	 */
	parp = Node_map(parh);
	dah = parp->n_dah;
	Node_unmap(parh, &parp);
	if (!Node2path(parh, path, _("set dir extattr"))) {
		mlog (MLOG_NORMAL | MLOG_WARNING | MLOG_TREE,  _(
		      "tree_extattr_recurse: Could not convert node to "
		      "path for %s\n"), path);
		return BOOL_TRUE;
	}
	if (dah != DAH_NULL) {
		ok = (*cbfunc)(path, dah);
	} else {
		ok = BOOL_TRUE;
	}

	return ok;
}

struct phcb {
	char *path1;
	char *path2;
	bool_t ok;
};

typedef struct phcb phcb_t;

static bool_t proc_hardlinks_cb(void *contextp, nh_t hardheadh);

static bool_t
proc_hardlinks(char *path1, char *path2)
{
	phcb_t phcb;

	/* have callback invoked for all hardheads
	 */
	phcb.path1 = path1;
	phcb.path2 = path2;
	phcb.ok = BOOL_TRUE;
	link_headiter(proc_hardlinks_cb, (void *)&phcb);
	return phcb.ok;
}

/* called for every hard head
 *
 * tes@sgi.com:
 * This code processes the hardlinks, extracting out a
 * src_list - real & !ref
 * dest_list - !real & ref
 * The src_list are the entries to delete and the dst_list
 * are the new_entries to create.
 * 1. If we have a src and a dst then we can do a rename.
 * 2. If we have extra src nodes then we can unlink them.
 * 3. If we have extra dst nodes then we can link them.
 * HOWEVER, the linking in of the new nodes is actually handled
 * in the restore_file_cb() code which on restoral creates
 * the file and all its hardlinks.
 * ALSO, I can not see how the src_list can have more than
 * 1 node in it, since in noref_elim_recurse() it unlinks all
 * extra deleted entries (real & !ref).
 * Thus, steps 2 and 3 are handled in noref_elim_recurse().
 */
static bool_t
proc_hardlinks_cb(void *contextp, nh_t hardheadh)
{
	phcb_t *phcbp = (phcb_t *)contextp;
	node_t *hardheadp;
	xfs_ino_t ino;
	gen_t gen;
	bool_t isdirpr;
	nh_t rnsrcheadh;
	nh_t rndstheadh;
	nh_t lnsrch;
	nh_t nh;
	link_iter_context_t link_iter_context;
	bool_t ok;
	int rval;

	/* skip directories
	 */
	hardheadp = Node_map(hardheadh);
	ino = hardheadp->n_ino;
	gen = hardheadp->n_gen;
	isdirpr = hardheadp->n_flags & NF_ISDIR;
	Node_unmap(hardheadh, &hardheadp);
	if (isdirpr) {
		return BOOL_TRUE;
	}

	mlog(MLOG_DEBUG | MLOG_TREE,
	      "processing hardlinks to %llu %u\n",
	      ino,
	      gen);

	/* first pass through hard link list: for each node, leave on
	 * list, unlink and place on rename src list, unlink and place on
	 * rename dst list, or unlink and discard. note a node available
	 * to link from, in case we need it.
	 */
	rnsrcheadh = NH_NULL;
	rndstheadh = NH_NULL;
	lnsrch = NH_NULL;
	link_iter_init(&link_iter_context, hardheadh);
	while ((nh = link_iter_next(&link_iter_context)) != NH_NULL) {

		node_t *np = Node_map(nh);
		bool_t isrealpr = np->n_flags & NF_REAL;
		bool_t isrefpr = np->n_flags & NF_REFED;
		bool_t isselpr = np->n_flags & NF_SUBTREE;

		/* if unrefed, unreal, free node etc. (sel doesn't matter)
		 */
		if (!isrealpr && !isrefpr) {
			mlog(MLOG_NITTY | MLOG_TREE,
			      "freeing node %x: not real, not referenced\n",
			      nh);
			link_iter_unlink(&link_iter_context, nh);
			Node_unmap(nh, &np);
			(void)disown(nh);
			Node_free(&nh);
			continue;
		}

		/* not real, refed, but not selected, can't help
		 */
		if (!isrealpr &&   isrefpr && !isselpr) {
			mlog(MLOG_NITTY | MLOG_TREE,
			      "skipping node %x: not selected\n",
			      nh);
			Node_unmap(nh, &np);
			continue;
		}

		/* if unreal, refed, sel, add to dst list,
		 */
		if (!isrealpr &&   isrefpr &&   isselpr) {
			mlog(MLOG_NITTY | MLOG_TREE,
			      "making node %x dst: "
			      "not real, refed, sel\n",
			      nh);
			link_iter_unlink(&link_iter_context, nh);
			np->n_lnkh = rndstheadh;
			rndstheadh = nh;
			Node_unmap(nh, &np);
			continue;
		}

		/* if real, unrefed, sel, add to src list
		 */
		if (isrealpr && !isrefpr &&   isselpr) {
			mlog(MLOG_NITTY | MLOG_TREE,
			      "making node %x src: real, not refed, sel\n",
			      nh);
			link_iter_unlink(&link_iter_context, nh);
			np->n_lnkh = rnsrcheadh;
			rnsrcheadh = nh;
			Node_unmap(nh, &np);
			continue;
		}

		/* would like to get rid of but not selected, or
		 * real and referenced, leave alone (sel doesn't matter).
		 * consider as a lnk src, since real and not going away.
		 */
		if (isrealpr && (isrefpr || !isselpr)) {
			mlog(MLOG_NITTY | MLOG_TREE,
			      "skipping node %x: %s\n",
			      nh,
			      isselpr ? "real and ref" : "real and not sel");
			Node_unmap(nh, &np);
			if (lnsrch == NH_NULL) {
				mlog(MLOG_NITTY | MLOG_TREE,
				      "node %x will be link src\n",
				      nh);
				lnsrch = nh;
			}
			continue;
		}
		assert(0);
	}

	/* now pass through dst list, doing renames if src list not empty,
	 * otherwise links if a lnk src available, otherwise put back in link
	 * list
	 */
	while (rndstheadh != NH_NULL) {
		nh_t dsth;
		node_t *dstp;
		bool_t successpr;

		dsth = rndstheadh;
		dstp = Node_map(dsth);
		rndstheadh = dstp->n_lnkh;
		dstp->n_lnkh = NH_NULL;
		Node_unmap(dsth, &dstp);

		/* build pathname to dst
		 */
		ok = Node2path(dsth, phcbp->path2, _("rename to"));
		if (!ok) {
			link_in(dsth);
			continue;
		}

		successpr = BOOL_FALSE;
		while (!successpr && rnsrcheadh != NH_NULL) {
			nh_t srch;
			nrh_t nrh;
			node_t *srcp;

			srch = rnsrcheadh;
			srcp = Node_map(srch);
			rnsrcheadh = srcp->n_lnkh;
			srcp->n_lnkh = NH_NULL;
			Node_unmap(srch, &srcp);

			/* build a path to src
			 */
			ok = Node2path(srch, phcbp->path1, _("rename from"));
			if (!ok) {
				link_in(srch);
				continue;
			}

			if (tranp->t_toconlypr) {
				rval = 0;
			} else {
				mlog(MLOG_TRACE | MLOG_TREE,
				      "rename nondir %s to %s\n",
				      phcbp->path1,
				      phcbp->path2);
				rval = rename(phcbp->path1,
					       phcbp->path2);
			}
			if (rval) {
				mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_TREE, _(
				      "unable to rename nondir "
				      "%s to %s: %s\n"),
				      phcbp->path1,
				      phcbp->path2,
				      strerror(errno));
				link_in(srch);
				continue;
			}

			nrh = disown(srch);
			if (nrh != NRH_NULL) {
				namreg_del(nrh);
			}
			Node_free(&srch);

			successpr = BOOL_TRUE;
		}

		/* tes@sgi.com: note: loop of one iteration only
		 */
		while (!successpr && lnsrch != NH_NULL) {
			ok = Node2path(lnsrch, phcbp->path1, _("link"));

			if (!ok) {
				mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_TREE, _(
				      "unable to use %s "
				      "as a hard link source\n"),
				      phcbp->path1);
				lnsrch = NH_NULL;
				continue;
			}

			if (tranp->t_toconlypr) {
				rval = 0;
			} else {
				mlog(MLOG_TRACE | MLOG_TREE,
				      "link nondir %s to %s\n",
				      phcbp->path1,
				      phcbp->path2);
				rval = link(phcbp->path1,
					     phcbp->path2);
			}
			if (rval) {
				mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_TREE,
				      "unable to link nondir "
				      "%s to %s: %s\n",
				      phcbp->path1,
				      phcbp->path2,
				      strerror(errno));
				break;
			}
			successpr = BOOL_TRUE;
		}

		if (!successpr) {
			mlog(MLOG_NITTY | MLOG_TREE,
			      "no link src for node %x\n",
			      dsth);
		} else {
			dstp = Node_map(dsth);
			dstp->n_flags |= NF_REAL;
			Node_unmap(dsth, &dstp);
		}

		link_in(dsth);
	}

	/* finally, pass through remaining src list, unlinking/disowning.
	 * tes@sgi.com: don't believe this will happen as this step
	 * should now be done in noref_elim_recurse().
	 */
	while (rnsrcheadh != NH_NULL) {
		nh_t srch;
		node_t *srcp;
		bool_t ok;

		srch = rnsrcheadh;
		srcp = Node_map(srch);
		rnsrcheadh = srcp->n_lnkh;
		srcp->n_lnkh = NH_NULL;
		Node_unmap(srch, &srcp);

		ok = Node2path(srch, phcbp->path1, _("unlink"));
		if (!ok) {
			link_in(srch);
			continue;
		}

		if (tranp->t_toconlypr) {
			rval = 0;
		} else {
			mlog(MLOG_TRACE | MLOG_TREE,
			      "unlink nondir %s\n",
			      phcbp->path1);
			rval = unlink(phcbp->path1);
		}
		if (rval) {
			mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_TREE, _(
			      "unable to unlink %s: %s\n"),
			      phcbp->path1,
			      strerror(errno));
			link_in(srch);
		} else {
			nrh_t nrh;
			nrh = disown(srch);
			if (nrh != NRH_NULL) {
				namreg_del(nrh);
			}
			Node_free(&srch);
		}
	}

	return BOOL_TRUE;
}

/* traverse tree depth-wise bottom-up for dirs no longer referenced.
 * if non-empty, move children to orphanage
 */
bool_t
tree_setattr(char *path)
{
	bool_t ok;
	node_t *rootp;

	ok = tree_setattr_recurse(persp->p_rooth, path);

	if (restore_rootdir_permissions && ok) {
		rootp = Node_map(persp->p_rooth);
		/* "." is cwd which is the destination dir */
		setdirattr(rootp->n_dah, ".");
		Node_unmap(persp->p_rooth, &rootp);
	}

	return ok;
}

static bool_t
tree_setattr_recurse(nh_t parh, char *path)
{
	node_t *parp = Node_map(parh);
	nh_t cldh = parp->n_cldh;
	Node_unmap(parh, &parp);
	while (cldh != NH_NULL) {
		nh_t nextcldh;

		/* get the node attributes
		 */
		node_t *cldp = Node_map(cldh);
		bool_t isdirpr = (cldp->n_flags & NF_ISDIR);
		bool_t isselpr = (cldp->n_flags & NF_SUBTREE);
		bool_t isrealpr = (cldp->n_flags & NF_REAL);
		dah_t dah = cldp->n_dah;

		/* get next cld
		 */
		nextcldh = cldp->n_sibh;
		Node_unmap(cldh, &cldp);

		/* if is a real selected dir, go ahead.
		 */
		if (isdirpr && isselpr && isrealpr) {
			bool_t ok;
			ok = tree_setattr_recurse(cldh, path); /* RECURSION */
			if (!ok) {
				Node_unmap(cldh, &cldp);
				return BOOL_FALSE;
			}
			if (dah != DAH_NULL) {
				bool_t ok;
				ok = Node2path(cldh, path, _("set dirattr"));
				if (ok) {
					setdirattr(dah, path);
				}
			}
		}

		cldh = nextcldh;
	}

	return BOOL_TRUE;
}

static void
setdirattr(dah_t dah, char *path)
{
	mode_t mode;
	struct utimbuf utimbuf;
	struct fsxattr fsxattr;
	int rval;
	size_t	hlen;
	void	*hanp;
	int fd = -1;

	if (dah == DAH_NULL)
		return;

	if (tranp->t_dstdirisxfspr) {
		if (path_to_handle(path, &hanp, &hlen)) {
			mlog(MLOG_NORMAL | MLOG_WARNING,
				_("path_to_handle of %s failed:%s\n"),
				path, strerror(errno));
		} else {
			fd = open_by_handle(hanp, hlen, O_RDONLY);
			if (fd < 0) {
				mlog(MLOG_NORMAL | MLOG_WARNING,
					_("open_by_handle of %s failed:%s\n"),
					path, strerror(errno));
			}
			free_handle(hanp, hlen);
		}
	}

	utimbuf.actime = dirattr_get_atime(dah);
	utimbuf.modtime = dirattr_get_mtime(dah);
	rval = utime(path, &utimbuf);
	if (rval) {
		mlog(MLOG_VERBOSE | MLOG_TREE, _(
		      "could not set access and modification times"
		      " of %s: %s\n"),
		      path,
		      strerror(errno));
	}
	mode = dirattr_get_mode(dah);
	if (persp->p_ownerpr) {
		rval = chown(path,
			      dirattr_get_uid(dah),
			      dirattr_get_gid(dah));
		if (rval) {
			mlog(MLOG_NORMAL | MLOG_TREE, _(
			      "chown (uid=%d, gid=%d) %s failed: %s\n"),
			      dirattr_get_uid(dah),
			      dirattr_get_gid(dah),
			      path,
			      strerror(errno));
		}
	}
	rval = chmod(path, mode);
	if (rval) {
		mlog(MLOG_NORMAL | MLOG_TREE, _(
		      "chmod %s failed: %s\n"),
		      path,
		      strerror(errno));
	}

	/* set the extended inode flags
	 */
	if (!tranp->t_dstdirisxfspr)
		return;

	memset((void *)&fsxattr, 0, sizeof(fsxattr));
	fsxattr.fsx_xflags = dirattr_get_xflags(dah);
	fsxattr.fsx_extsize = dirattr_get_extsize(dah);
	fsxattr.fsx_projid = dirattr_get_projid(dah);

	rval = ioctl(fd,
		      XFS_IOC_FSSETXATTR,
		      (void *)&fsxattr);
	if (rval < 0) {
		mlog(MLOG_NORMAL | MLOG_WARNING,
		     _("attempt to set "
		       "extended attributes "
		       "(xflags 0x%x, "
		       "extsize = 0x%x, "
		       "projid = 0x%x) "
		       "of %s failed: "
		       "%s\n"),
		     fsxattr.fsx_xflags,
		     fsxattr.fsx_extsize,
		     fsxattr.fsx_projid,
		     path,
		     strerror(errno));
	}
	(void)close(fd);
}

/* deletes orphanage if empty, else warns
 */
bool_t
tree_delorph(void)
{
	int rval;

	rval = rmdir(tranp->t_orphdir);
	if (rval) {
		if (errno == EEXIST) {
			mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_TREE, _(
			     "unable to rmdir %s: not empty\n"),
			     tranp->t_orphdir);
		} else {
			mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_TREE, _(
			     "unable to rmdir %s: %s\n"),
			     tranp->t_orphdir,
			     strerror(errno));
		}
	}

	return BOOL_TRUE;
}


/* definition of locally defined static functions ****************************/


/* interactive subtree abstraction *******************************************/

static void tsi_cmd_inst(void *, dlog_pcbp_t, void *);
static void tsi_cmd_pwd(void *, dlog_pcbp_t, void *);
static void tsi_cmd_ls(void *, dlog_pcbp_t, void *);
static void tsi_cmd_cd(void *, dlog_pcbp_t, void *);
static void tsi_cmd_add(void *, dlog_pcbp_t, void *);
static void tsi_cmd_delete(void *, dlog_pcbp_t, void *);
static void tsi_cmd_extract(void *, dlog_pcbp_t, void *);
static void tsi_cmd_quit(void *, dlog_pcbp_t, void *);
static void tsi_cmd_help(void *, dlog_pcbp_t, void *);
static void tsi_cmd_parse(char *);
static dlog_ucbp_t tsi_cmd_match(void);

#define PREAMBLEMAX	3
#define ACKMAX		3
#define POSTAMBLEMAX	3

bool_t
tree_subtree_inter(void)
{
	fold_t fold;
	char *preamblestr[PREAMBLEMAX];
	size_t preamblecnt;
	char *ackstr[ACKMAX];
	size_t ackcnt;
	char *postamblestr[POSTAMBLEMAX];
	size_t postamblecnt;

	dlog_ucbp_t cmdp;

restart:
	/* make the current working directory the root of the fs
	 */
	tranp->t_inter.i_cwdh = persp->p_rooth;

	/* begin the dialog
	 */
	preamblecnt = 0;
	fold_init(fold, _("subtree selection dialog"), '=');
	preamblestr[preamblecnt++ ] = "\n";
	preamblestr[preamblecnt++] = fold;
	preamblestr[preamblecnt++ ] = "\n\n";
	assert(preamblecnt <= PREAMBLEMAX);
	dlog_begin(preamblestr, preamblecnt);

	/* execute commands until time to extract or quit. always begin with
	 * an implicit instructions command. if see SIGINT, give main thread
	 * dialog a chance to decide if a session interrupt is wanted.
	 */
	cmdp = tsi_cmd_inst;
	do {
		char buf[MAXPATHLEN];
		const ix_t abortix = 1;
		const ix_t sigintix = 2;
		const ix_t okix = 3;
		ix_t responseix;

		/* execute command and get response
		 */
		responseix = dlog_string_query(cmdp,
					        0,
					        buf,
					        sizeof(buf),
					        0,
					        IXMAX,    /* timeout ix */
					        sigintix, /* sigint ix */
					        abortix,  /* sighup ix */
						abortix,  /* sigquit ix */
						okix);   /* ok ix */

		/* ack the response
		 */
		ackcnt = 0;
		if (responseix == sigintix) {
			ackstr[ackcnt++ ] = _("keyboard interrupt\n");
		} else if (responseix == abortix) {
			ackstr[ackcnt++ ] = _("abort\n");
		} else {
			assert(responseix == okix);
		}
		assert(ackcnt <= ACKMAX);
		dlog_string_ack(ackstr,
				 ackcnt);

		/* exception handling
		 */
		if (responseix != okix) {
			/* if exception, end the dialog. may restart
			 * if operator decidesd not to intr.
			 */
			postamblecnt = 0;
			fold_init(fold, _("end dialog"), '-');
			postamblestr[postamblecnt++ ] = "\n";
			postamblestr[postamblecnt++] = fold;
			postamblestr[postamblecnt++ ] = "\n\n";
			assert(postamblecnt <= POSTAMBLEMAX);
			dlog_end(postamblestr, postamblecnt);

			/* if sighup or sigquit, immediately quit
			 */
			if (responseix == abortix) {
				return BOOL_FALSE;
			}

			/* if sigint, allow main thread to decide if
			 * operator really wants to quit
			 */
			assert(responseix == sigintix);
			if (cldmgr_stop_requested()) {
				return BOOL_FALSE;
			}
			sleep(1); /* to allow main thread to begin dialog */
			mlog(MLOG_NORMAL | MLOG_BARE | MLOG_TREE,
			      ""); /* block until main thread dialog done */
			sleep(1); /* let main thread ask children to die */
			if (cldmgr_stop_requested()) {
				return BOOL_FALSE;
			}
			mlog(MLOG_DEBUG | MLOG_TREE,
			      "retrying interactive subtree selection dialog\n");
			goto restart;
		}


		tsi_cmd_parse(buf);
		cmdp = tsi_cmd_match();
		if (!cmdp) {
			cmdp = tsi_cmd_help;
		}
	} while (cmdp != tsi_cmd_quit && cmdp != tsi_cmd_extract);

	postamblecnt = 0;
	fold_init(fold, _("end dialog"), '-');
	postamblestr[postamblecnt++ ] = "\n";
	postamblestr[postamblecnt++] = fold;
	postamblestr[postamblecnt++ ] = "\n\n";
	assert(postamblecnt <= POSTAMBLEMAX);
	dlog_end(postamblestr, postamblecnt);

	/* pv 773569 - quit is not a reason to consider session
         * to be interrupted (we haven't started yet) so just unmark
         * any selected directories and return */
	if (cmdp == tsi_cmd_quit) {
		mlog(MLOG_NORMAL, _("Unmark and quit\n"));
		selsubtree(persp->p_rooth, BOOL_FALSE);
	}

	return BOOL_TRUE;
}

static void
tsi_cmd_inst(void *ctxp,
	      dlog_pcbp_t pcb,
	      void *pctxp)
{
	tsi_cmd_help(ctxp, pcb, pctxp);
}

static void
tsi_cmd_pwd(void *ctxp,
	     dlog_pcbp_t pcb,
	     void *pctxp)
{
	/* special case root
	 */
	if (tranp->t_inter.i_cwdh == persp->p_rooth) {
		(*pcb )(pctxp, "cwd is fs root\n");
		return;
	}

	/* ascend tree recursively, print path on way back
	 */
	tsi_cmd_pwd_recurse(ctxp, pcb, pctxp, tranp->t_inter.i_cwdh);
	(*pcb )(pctxp, "\n");
}

static void
tsi_cmd_pwd_recurse(void *ctxp,
		     dlog_pcbp_t pcb,
		     void *pctxp,
		     nh_t nh)
{
	node_t *np;
	register nh_t parh;
	/* REFERENCED */
	register int namelen;
	nrh_t nrh;

	assert(nh != NH_NULL);

	np = Node_map(nh);
	nrh = np->n_nrh;
	parh = np->n_parh;
	Node_unmap(nh, &np);
	if (parh != persp->p_rooth) {
		tsi_cmd_pwd_recurse(ctxp, pcb, pctxp, parh);
			/* RECURSION */
		(*pcb )(pctxp, "/");
	}
	assert(nrh != NRH_NULL);
	namelen = namreg_get(nrh,
			      tranp->t_inter.i_name,
			      sizeof(tranp->t_inter.i_name));
	assert(namelen > 0);
	(*pcb)(pctxp, tranp->t_inter.i_name);
}

/* ARGSUSED */
static void
tsi_cmd_ls(void *ctxp,
	    dlog_pcbp_t pcb,
	    void *pctxp)
{
	size_t argc = tranp->t_inter.i_argc;
	char *arg = (argc > 1) ? tranp->t_inter.i_argv[1] : 0;
	bool_t ok;

	nh_t cldh;
	nh_t parh;
	nh_t namedh;
	xfs_ino_t ino;
	bool_t isdirpr;
	bool_t isselpr;

	/* walk the tree according to the path argument, to get
	 * the named node.
	 */
	ok = tsi_walkpath(arg,
			   persp->p_rooth,
			   tranp->t_inter.i_cwdh,
			   pcb,
			   pctxp,
			   &namedh,
			   &parh,
			   &cldh,
			   &ino,
			   &isdirpr,
			   &isselpr);
	if (!ok) {
		return;
	}

	/* if named is not a dir, just display named
	 */
	if (!isdirpr) {
		(*pcb)(pctxp,
			   "    %s %10llu %s%s\n",
			   isselpr ? "*" : " ",
			   ino,
			   tranp->t_inter.i_name,
			   isdirpr ? "/" : " ");

		return;
	}

	/* iterate through the directory, printing all matching entries.
	 * hide the orphanage.
	 */
	while (cldh != NH_NULL) {
		node_t *cldp;
		nrh_t nrh;
		nh_t nextcldh;
		cldp = Node_map(cldh);
		nrh = cldp->n_nrh;
		nextcldh = cldp->n_sibh;
		isdirpr = (cldp->n_flags & NF_ISDIR);
		isselpr = (cldp->n_flags & NF_SUBTREE);
		ino = cldp->n_ino;
		Node_unmap(cldh, &cldp);
		if (cldh != persp->p_orphh) {
			/* REFERENCED */
			int namelen;
			namelen = namreg_get(nrh,
					      tranp->t_inter.i_name,
					      sizeof(tranp->t_inter.i_name));
			assert(namelen > 0);
			(*pcb)(pctxp,
				   "    %s %10llu %s%s\n",
				   isselpr ? "*" : " ",
				   ino,
				   tranp->t_inter.i_name,
				   isdirpr ? "/" : " ");
		}
		cldh = nextcldh;
	}
}

/* ARGSUSED */
static void
tsi_cmd_cd(void *ctxp,
	    dlog_pcbp_t pcb,
	    void *pctxp)
{
	size_t argc = tranp->t_inter.i_argc;
	char *arg = (argc > 1) ? tranp->t_inter.i_argv[1] : 0;

	nh_t cldh;
	nh_t parh;
	nh_t namedh;
	xfs_ino_t ino;
	bool_t isdirpr;
	bool_t isselpr;
	bool_t ok;

	/* walk the tree according to the path argument, to get
	 * the named node.
	 */
	ok = tsi_walkpath(arg,
			   persp->p_rooth,
			   tranp->t_inter.i_cwdh,
			   pcb,
			   pctxp,
			   &namedh,
			   &parh,
			   &cldh,
			   &ino,
			   &isdirpr,
			   &isselpr);
	if (!ok) {
		return;
	}

	/* if named is not a dir, complain
	 */
	if (!isdirpr) {
		assert(arg);
		(*pcb)(pctxp,
			   _("%s is not a directory\n"),
			   arg);

		return;
	}

	/* change the current working directory
	 */
	tranp->t_inter.i_cwdh = namedh;
}

/* ARGSUSED */
static void
tsi_cmd_add(void *ctxp,
	     dlog_pcbp_t pcb,
	     void *pctxp)
{
	size_t argc = tranp->t_inter.i_argc;
	char *arg = (argc > 1) ? tranp->t_inter.i_argv[1] : 0;

	nh_t cldh;
	nh_t parh;
	nh_t namedh;
	xfs_ino_t ino;
	bool_t isdirpr;
	bool_t isselpr;
	bool_t ok;

	/* walk the tree according to the path argument, to get
	 * the named node.
	 */
	ok = tsi_walkpath(arg,
			   persp->p_rooth,
			   tranp->t_inter.i_cwdh,
			   pcb,
			   pctxp,
			   &namedh,
			   &parh,
			   &cldh,
			   &ino,
			   &isdirpr,
			   &isselpr);
	if (!ok) {
		return;
	}

	selsubtree(namedh, BOOL_TRUE);
}

/* ARGSUSED */
static void
tsi_cmd_delete(void *ctxp,
		dlog_pcbp_t pcb,
		void *pctxp)
{
	size_t argc = tranp->t_inter.i_argc;
	char *arg = (argc > 1) ? tranp->t_inter.i_argv[1] : 0;

	nh_t cldh;
	nh_t parh;
	nh_t namedh;
	xfs_ino_t ino;
	bool_t isdirpr;
	bool_t isselpr;
	bool_t ok;

	/* walk the tree according to the path argument, to get
	 * the named node.
	 */
	ok = tsi_walkpath(arg,
			   persp->p_rooth,
			   tranp->t_inter.i_cwdh,
			   pcb,
			   pctxp,
			   &namedh,
			   &parh,
			   &cldh,
			   &ino,
			   &isdirpr,
			   &isselpr);
	if (!ok) {
		return;
	}

	selsubtree(namedh, BOOL_FALSE);
}

/* ARGSUSED */
static void
tsi_cmd_extract(void *ctxp,
		 dlog_pcbp_t pcb,
		 void *pctxp)
{
}

/* ARGSUSED */
static void
tsi_cmd_quit(void *ctxp,
	      dlog_pcbp_t pcb,
	      void *pctxp)
{
}

static int parse(int slotcnt, char **slotbuf, char *string);

static void
tsi_cmd_parse(char *buf)
{
	int wordcnt;

	if (!buf) {
		tranp->t_inter.i_argc = 0;
		return;
	}

	wordcnt = parse(INTER_ARGMAX, tranp->t_inter.i_argv, buf);

	tranp->t_inter.i_argc = (size_t)min(max(0, wordcnt), INTER_ARGMAX);
}

struct tsi_cmd_tbl {
	char *tct_pattern;
	char *tct_help;
	dlog_ucbp_t tct_ucb;
	size_t tct_argcmin;
	size_t tct_argcmax;
};

typedef struct tsi_cmd_tbl tsi_cmd_tbl_t;

static tsi_cmd_tbl_t tsi_cmd_tbl[] = {
	{ "pwd",	"",				tsi_cmd_pwd,	1, 1 },
	{ "ls",		"[ <path> ]",			tsi_cmd_ls,	1, 2 },
	{ "cd",		"[ <path> ]",			tsi_cmd_cd,	1, 2 },
	{ "add",	"[ <path> ]",			tsi_cmd_add,	1, 2 },
	{ "delete",	"[ <path> ]",			tsi_cmd_delete,	1, 2 },
	{ "extract",	"",				tsi_cmd_extract,1, 1 },
	{ "quit",	"",				tsi_cmd_quit,	1, 1 },
	{ "help",	"",				tsi_cmd_help,	1, 1 },
};

static dlog_ucbp_t
tsi_cmd_match(void)
{
	tsi_cmd_tbl_t *tblp = tsi_cmd_tbl;
	tsi_cmd_tbl_t *tblendp = tsi_cmd_tbl
				 +
				 sizeof(tsi_cmd_tbl)
				 /
				 sizeof(tsi_cmd_tbl[0]);

	if (tranp->t_inter.i_argc == 0) {
		return 0;
	}

	for (; tblp < tblendp; tblp++) {
		if (!strncmp(tranp->t_inter.i_argv[0],
				tblp->tct_pattern,
				strlen(tranp->t_inter.i_argv[0]))) {
			break;
		}
	}

	if (tblp == tblendp) {
		return 0;
	}

	assert(tblp->tct_argcmin != 0);
	if (tranp->t_inter.i_argc < tblp->tct_argcmin) {
		return 0;
	}

	if (tranp->t_inter.i_argc > tblp->tct_argcmax) {
		return 0;
	}

	return tblp->tct_ucb;
}

/* ARGSUSED */
static void
tsi_cmd_help(void *ctxp,
	      dlog_pcbp_t pcb,
	      void *pctxp)
{
	tsi_cmd_tbl_t *tblp = tsi_cmd_tbl;
	tsi_cmd_tbl_t *tblendp = tsi_cmd_tbl
				 +
				 sizeof(tsi_cmd_tbl)
				 /
				 sizeof(tsi_cmd_tbl[0]);

	(*pcb )(pctxp, _("the following commands are available:\n"));
	for (; tblp < tblendp; tblp++) {
		(*pcb)(pctxp,
			   "\t%s %s\n",
			   tblp->tct_pattern,
			   tblp->tct_help);
	}
}

/* walks the tree following the given path.
 * returns FALSE if syntax error encountered.
 * returns by reference handles to the named node, its parent,
 * the first child in its cld list, its ino, if it is a directory,
 * and if it is selected.
 * optionally given a dlog print func and context, to be used for diag output.
 */
static bool_t
tsi_walkpath(char *arg, nh_t rooth, nh_t cwdh,
	      dlog_pcbp_t pcb, void *pctxp,
	      nh_t *namedhp, nh_t *parhp, nh_t *cldhp,
	      xfs_ino_t *inop, bool_t *isdirprp, bool_t *isselprp)
{
	nh_t namedh;
	nh_t parh;
	nh_t cldh;
	node_t *namedp;
	char *path;
	char nbuf[NAME_MAX + 1];
	xfs_ino_t ino;
	bool_t isdirpr;
	bool_t isselpr;


	/* correct arg if ends with slash (if arg is "/", ok)
	 */
	if (arg && strlen(arg) > 1 && arg[strlen(arg) - 1] == '/') {
		arg[strlen(arg) - 1] = 0;
	}

	/* use path to walk down the path argument
	 */
	path = arg;

	/* walk the tree beginning either at the root node
	 * or at the current working directory
	 */
	if (path && *path == '/') {
		assert(rooth != NH_NULL);
		namedh = rooth;
		path++;
	} else {
		assert(cwdh != NH_NULL);
		namedh = cwdh;
	}

	/* get the parent of the starting point, and its cld list
	 */
	namedp = Node_map(namedh);
	parh = namedp->n_parh;
	cldh = namedp->n_cldh;
	ino = namedp->n_ino;
	isselpr = (namedp->n_flags & NF_SUBTREE);
	assert(namedp->n_flags & NF_ISDIR);
	Node_unmap(namedh, &namedp);
	isdirpr = BOOL_TRUE;

	/* walk the tree from the starting point following the path arg.
	 * on leaving this loop, the following will be valid:
	 *	namedh  - the handle of the node named by path arg;
	 *	parh - the parent of the named node;
	 *	isdirpr - TRUE if named node is a directory;
	 *	cldh - the first child in the named node's cld list.
	 */
	for (;;) {
		size_t namelen;
		char *strpatchp;
		nh_t sibh;

		/* if no path arg, break
		 */
		if (!path) {
			break;
		}

		/* clean out leading slashes. these can occur if the
		 * path arg is ".../////..." or "////..."
		 */
		while (*path == '/') {
			path++;
		}

		/* if empty path arg, break
		 */
		if (!strlen(path)) {
			break;
		}

		/* copy the first name from the path, and advance
		 * the path pointer.
		 */
		namelen = strcspn(path, "/");
		assert(namelen < sizeof(nbuf));
		strncpy(nbuf, path, namelen);
		nbuf[namelen] = 0;
		path += namelen;
		if (*path) {
			assert(*path == '/');
			strpatchp = path;
			*strpatchp = 0;
			path++;
		} else {
			strpatchp = 0;
		}

		/* be sure the named node is a dir
		 */
		if (!isdirpr) {
			if (pcb) {
				(*pcb)(pctxp, _(
					   "parent of %s is not a directory\n"),
					   arg);
			}
			return BOOL_FALSE;
		}

		/* special case "."
		 */
		if (!strcmp(nbuf, ".")) {
			if (strpatchp) {
				*strpatchp = '/';
			}
			continue;
		}

		/* special case ".."
		 */
		if (!strcmp(nbuf, "..")) {
			if (parh == NH_NULL) {
				if (pcb) {
					(*pcb)(pctxp, _(
						   "%s above root\n"),
						   arg);
				}
				return BOOL_FALSE;
			}
			namedh = parh;
			namedp = Node_map(namedh);
			parh = namedp->n_parh;
			cldh = namedp->n_cldh;
			ino = namedp->n_ino;
			isselpr = (namedp->n_flags & NF_SUBTREE);
			Node_unmap(namedh, &namedp);
			if (strpatchp) {
				*strpatchp = '/';
			}
			continue;
		}

		/* look for child with right name
		 */
		sibh = cldh;
		while (sibh != NH_NULL) {
			node_t *sibp;
			nh_t nextsibh;
			nrh_t nrh;
			/* REFERENCED */
			int siblen;

			sibp = Node_map(sibh);
			nrh = sibp->n_nrh;
			nextsibh = sibp->n_sibh;
			cldh = sibp->n_cldh;
			ino = sibp->n_ino;
			isselpr = (sibp->n_flags & NF_SUBTREE);
			isdirpr = (sibp->n_flags & NF_ISDIR);
			Node_unmap(sibh, &sibp);
			assert(nrh != NRH_NULL || sibh == persp->p_orphh);
			if (nrh != NRH_NULL) {
				siblen = namreg_get(nrh,
						     tranp->t_inter.i_name,
					       sizeof(tranp->t_inter.i_name));
				assert(siblen > 0);
				if (!strcmp(nbuf, tranp->t_inter.i_name)) {
					break;
				}
			}
			sibh = nextsibh;
		}

		/* if no match, complain
		 */
		if (sibh == NH_NULL) {
			if (pcb) {
				(*pcb)(pctxp, _(
					   "%s not found\n"),
					   arg);
			}
			return BOOL_FALSE;
		}

		/* continue search. cldh, ino and isdirpr
		 * set in inner loop above
		 */
		parh = namedh;
		namedh = sibh;
		if (strpatchp) {
			*strpatchp = '/';
		}
	}
	*namedhp = namedh;
	*parhp = parh;
	*cldhp = cldh;
	*inop = ino;
	*isdirprp = isdirpr;
	*isselprp = isselpr;

	return BOOL_TRUE;
}

/* Node abstraction *********************************************************/

static nh_t
Node_alloc(xfs_ino_t ino, gen_t gen, nrh_t nrh, dah_t dah, size_t flags)
{
	nh_t nh;
	node_t *np;

	nh = node_alloc();
	if (nh == NH_NULL)
	    return NH_NULL;
	np = Node_map(nh);
	np->n_ino = ino;
	np->n_nrh = nrh;
	np->n_dah = dah;
	np->n_hashh = NH_NULL;
	np->n_parh = NH_NULL;
	np->n_sibh = NH_NULL;
	np->n_sibprevh = NH_NULL;
	np->n_cldh = NH_NULL;
	np->n_lnkh = NH_NULL;
	np->n_gen = gen;
	np->n_flags = (u_char_t)flags;
	memset(np->n_pad, 0, sizeof(np->n_pad));
	Node_unmap(nh, &np);
	return nh;
}

static void
Node_free(nh_t *nhp)
{
	node_t *np;
	np = Node_map(*nhp);
	np->n_ino = 0;
	np->n_gen = 0;
	if (np->n_nrh != NRH_NULL) {
		namreg_del(np->n_nrh);
		np->n_nrh = NRH_NULL;
	}
	if (np->n_dah != DAH_NULL) {
		dirattr_del(np->n_dah);
		np->n_dah = DAH_NULL;
	}
	np->n_flags = 0;
	np->n_parh = NH_NULL;
	np->n_sibh = NH_NULL;
	np->n_sibprevh = NH_NULL;
	np->n_cldh = NH_NULL;
	np->n_lnkh = NH_NULL;
	Node_unmap(*nhp, &np);
	node_free(nhp);
}

/*
 * NOTE: Does error handling here and exits.
 */
static node_t *
Node_map(nh_t nh)
{
	node_t *n = (node_t *)node_map(nh);
	if (n == NULL) {
		mlog(MLOG_ERROR | MLOG_TREE, _(
			"failed to map in node (node handle: %u)\n"), nh);
		exit(EXIT_ERROR);
	}
	return n;
}

static void
Node_unmap(nh_t nh, node_t **npp)
{
	node_unmap(nh, (void **)npp);
}

/* builds a pathname for the specified node, relative to root
 * returns FALSE if pathname too long
 */
static bool_t
Node2path(nh_t nh, char *path, char *errmsg)
{
	int remainingcnt;
	strcpy(path, "."); /* in case root node passed in */
	remainingcnt = Node2path_recurse(nh, path, MAXPATHLEN, 0);
	if (remainingcnt <= 0) {
		node_t *np = Node_map(nh);
		xfs_ino_t ino = np->n_ino;
		gen_t gen = np->n_gen;
		Node_unmap(nh, &np);
		mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_TREE, _(
		      "unable %s ino %llu gen %u: "
		      "relative pathname too long (partial %s)\n"),
		      errmsg,
		      ino,
		      gen,
		      path);
		return BOOL_FALSE;
	} else {
		return BOOL_TRUE;
	}
}

/* returns how much of the buffer remains, assuming the buffer size is
 * MAXPATHLEN. always null-terminates, but null char not counted in return.
 * works because the buffer size is secretly 2 * MAXPATHLEN.
 */
static int
Node2path_recurse(nh_t nh, char *buf, int bufsz, int level)
{
	static __thread path_cache_t cache = { NH_NULL, 0, "" };
	node_t *np;
	nh_t parh;
	xfs_ino_t ino;
	gen_t gen;
	nrh_t nrh;
	char *oldbuf;
	int oldbufsz;
	int namelen;

	/* recursion termination
	 */
	if (nh == persp->p_rooth) {
		return bufsz;
	}

	/* if we have a cache hit, no need to recurse any further
	 */
	if (nh == cache.nh) {
		assert(bufsz > cache.len);
		strcpy(buf, cache.buf);
		return bufsz - cache.len;
	}

	/* extract useful node members
	 */
	np = Node_map(nh);
	parh = np->n_parh;
	ino = np->n_ino;
	gen = np->n_gen;
	nrh = np->n_nrh;
	Node_unmap(nh, &np);

	/* build path to parent
	 */
	oldbuf = buf;
	oldbufsz = bufsz;
	bufsz = Node2path_recurse(parh, buf, bufsz, level+1); /* RECURSION */
	if (bufsz <= 0) {
		return bufsz;
	}
	buf += oldbufsz - bufsz;

	/* insert slash if parent not root
	 */
	if (parh != persp->p_rooth) {
		assert(bufsz + MAXPATHLEN >= 2);
		*buf++ = '/';
		*(buf + 1) = 0;
		bufsz--;
		if (bufsz <= 0) {
			return bufsz;
		}
	}

	/* append entry name: special case if in orphanage
	 */
	if (parh == persp->p_orphh) {
		namelen = sprintf(buf, "%llu.%u", (unsigned long long)ino, gen);
	} else if (nh == persp->p_orphh) {
		namelen = sprintf(buf, "%s", orphname);
	} else {
		assert(nrh != NRH_NULL);
		namelen = namreg_get(nrh, buf, (size_t)bufsz + MAXPATHLEN);
		assert(namelen > 0);
	}

	/* update remaining buffer size
	 */
	bufsz -= namelen;
	assert(bufsz + MAXPATHLEN > 0);

	/* update the cache if we're the target's parent
	 * (and the pathname is not too long)
	 */
	if (level == 1 && bufsz > 0) {
		cache.nh = nh;
		strcpy(cache.buf, oldbuf);
		cache.len = oldbufsz - bufsz;
	}

	/* return remaining buffer size
	 */
	return bufsz;
}

/* family abstraction *********************************************************/

static void
adopt(nh_t parh, nh_t cldh, nrh_t nrh)
{
	node_t *parp;
	node_t *cldp;
	node_t *sibp;

#ifdef TREE_DEBUG
	mlog(MLOG_DEBUG | MLOG_TREE,
	   "adopt(parent=%llu,child=%llu,node=%llu): \n",
	   parh, cldh, nrh);
#endif

	/* fix up our child - put at front of child list */
	cldp = Node_map(cldh);
	cldp->n_parh = parh;
	cldp->n_nrh = nrh;
	parp = Node_map(parh);
	cldp->n_sibh = parp->n_cldh;
	cldp->n_sibprevh = NH_NULL;
	Node_unmap(cldh, &cldp);

	/* fix up old first child i.e. child's new sibling */
	if (parp->n_cldh != NH_NULL) { /* if parent has a child */
	    sibp = Node_map(parp->n_cldh);
	    sibp->n_sibprevh = cldh;
	    Node_unmap(parp->n_cldh, &sibp);
	}

        /* fix up parent */
	parp->n_cldh = cldh;
	Node_unmap(parh, &parp);
}

static nrh_t
disown(nh_t cldh)
{
	node_t *cldp;
	nrh_t nrh;
	nh_t parh;
	node_t *parp;

	cldp = Node_map(cldh);

	nrh = cldp->n_nrh;

	parh = cldp->n_parh;
	assert(parh != NH_NULL);
	if (parh == NH_NULL) {
		mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_TREE, _(
		      "attempt to disown child "
		      "which has no parent!\n"));
		return nrh;
	}
	parp = Node_map(parh);
	assert(parp->n_cldh != NH_NULL);
	if (parp->n_cldh == NH_NULL) {
		mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_TREE, _(
		      "attempt to disown child "
		      "when parent has no children!\n"));
		return nrh;
	}
	if (parp->n_cldh == cldh) {
		/* child is the first one in the child list */
		parp->n_cldh = cldp->n_sibh;
		if (cldp->n_sibh != NH_NULL) {
			node_t *sibp = Node_map(cldp->n_sibh);
			sibp->n_sibprevh = NH_NULL;
			Node_unmap(cldp->n_sibh, &sibp);
		}
	} else {
		/* child is further down the child list */
		/* use double link list to find previous link */
		nh_t prevcldh = cldp->n_sibprevh;
		node_t *prevcldp;

		assert(prevcldh != NH_NULL); /* must be a previous */
		prevcldp = Node_map(prevcldh);

		/* fix up previous */
		prevcldp->n_sibh = cldp->n_sibh;
		Node_unmap(prevcldh, &prevcldp);

		/* fix up next */
		if (cldp->n_sibh != NH_NULL) {
			node_t *sibp = Node_map(cldp->n_sibh);
			sibp->n_sibprevh = prevcldh;
			Node_unmap(cldp->n_sibh, &sibp);
		}
	}
	Node_unmap(parh, &parp);
	cldp->n_parh = NH_NULL;
	cldp->n_sibh = NH_NULL;
	cldp->n_sibprevh = NH_NULL;
	Node_unmap(cldh, &cldp);

	return nrh;
}

/* recursively marks all nodes in subtree as selected or not selected
 * for subtree restoral. adjusts ancestors flags accordingly. also adjusts
 * inomap, which will be used by content.c to see if a media file contains
 * any nondirs which might need to be restored.
 */
static void
selsubtree(nh_t nh, bool_t sensepr)
{
	node_t *np;
	nh_t parh;

	/* first mark the subtree
	 */
	selsubtree_recurse_down(nh, sensepr);

	/* get parent
	 */
	np = Node_map(nh);
	parh = np->n_parh;
	Node_unmap(nh, &np);

	/* next adjust ancestory
	 */
	while (parh != NH_NULL) {
		node_t *parp;
		nh_t newparh;

		parp = Node_map(parh);
		if (sensepr) {
			parp->n_flags |= NF_SUBTREE;
		} else {
			bool_t atleastonechildselpr = BOOL_FALSE;
			nh_t cldh = parp->n_cldh;
			while (cldh != NH_NULL) {
				node_t *cldp;
				nh_t nextcldh;
				bool_t cldsensepr;
				cldp = Node_map(cldh);
				cldsensepr = (cldp->n_flags & NF_SUBTREE)
					     ?
					     BOOL_TRUE
					     :
					     BOOL_FALSE;
				nextcldh = cldp->n_sibh;
				Node_unmap(cldh, &cldp);
				if (cldsensepr) {
					atleastonechildselpr = BOOL_TRUE;
					break;
				}
				cldh = nextcldh;
			}
			if (!atleastonechildselpr) {
				parp->n_flags &= ~NF_SUBTREE;
				/* DBG could break out here (remember to unmap!)
				 */
			}
		}
		newparh = parp->n_parh;
		Node_unmap(parh, &parp);
		parh = newparh;
	}
}

static void
selsubtree_recurse_down(nh_t nh, bool_t sensepr)
{
	nh_t cldh;

	/* first mark the node indicated, and get head of cld list
	 */
	{
		node_t *np;
		bool_t isdirpr;
		xfs_ino_t ino;
		gen_t gen;

		np = Node_map(nh);
		if (sensepr) {
			np->n_flags |= NF_SUBTREE;
		} else {
			np->n_flags &= ~NF_SUBTREE;
		}
		cldh = np->n_cldh;
		ino = np->n_ino;
		gen = np->n_gen;
		isdirpr = (np->n_flags & NF_ISDIR);
		Node_unmap(nh, &np);
		if (!isdirpr) {
			if (sensepr) {
				inomap_rst_add(ino);
			} else {
				/* check hardlist: don't del unless none needed
				 */
				nh_t nh;
				bool_t neededpr = BOOL_FALSE;
				for (nh = link_hardh(ino, gen)
				      ;
				      nh != NH_NULL
				      ;
				      nh = link_nexth(nh)) {
					node_t *np;
					u_char_t flags;
					np = Node_map(nh);
					flags = np->n_flags;
					Node_unmap(nh, &np);
					if (flags & NF_SUBTREE) {
						neededpr = BOOL_TRUE;
						break;
					}
				}
				if (!neededpr) {
					inomap_rst_del(ino);
				}
			}
		}
	}

	/* then mark all of its children. be sure to skip the orphanage!!!
	 */
	while (cldh != NH_NULL) {
		node_t *cldp;
		nh_t nextcldh;

		if (cldh != persp->p_orphh) {
			selsubtree_recurse_down(cldh, sensepr);
		}
		cldp = Node_map(cldh);
		nextcldh = cldp->n_sibh;
		Node_unmap(cldh, &cldp);
		cldh = nextcldh;
	}
}


/* link abstraction *********************************************************/

/* returns handle to head of hard link list
 */
static nh_t
link_hardh(xfs_ino_t ino, gen_t gen)
{
	nh_t tmp = hash_find(ino, gen);

	/*
	 * XXX (another workaround): the simply way is that don't reuse node_t
	 * with gen = 0 created in tree_init(). Otherwise, it could cause
	 * xfsrestore: tree.c:1003: tree_addent: Assertion
	 * `hardp->n_nrh != NRH_NULL' failed.
	 * and that node_t is a dir node but the fake rootino could be a non-dir
	 * plus reusing it could cause potential loop in tree hierarchy.
	 */
	if (need_fixrootdir == BOOL_TRUE &&
	    ino == persp->p_rootino && gen == 0 &&
	    orig_rooth == NH_NULL) {
		mlog(MLOG_NORMAL,
_("link out fake rootino %llu with gen=0 created in tree_init()\n"), ino);
		link_out(tmp);
		orig_rooth = tmp;
		return NH_NULL;
	}
	return tmp;
}

/* returns following node in hard link list
 */
static nh_t
link_nexth(nh_t nh)
{
	node_t *np;
	nh_t nexth;

	np = Node_map(nh);
	nexth = np->n_lnkh;
	Node_unmap(nh, &np);
	return nexth;
}

/* searches hard link list for exact match.
 * returns hard link list head
 */
static nh_t
link_matchh(nh_t hardh, nh_t parh, char *name)
{
	while (hardh != NH_NULL) {
		node_t *np;
		nh_t nexth;
		np = Node_map(hardh);
		if (np->n_parh == parh) {
			/* REFERENCED */
			int namelen;
			namelen = namreg_get(np->n_nrh,
					      tranp->t_namebuf,
					      sizeof(tranp->t_namebuf));
			assert(namelen > 0);
			if (!strcmp(name, tranp->t_namebuf)) {
				Node_unmap(hardh, &np);
				break;
			}
		}
		nexth = np->n_lnkh;
		Node_unmap(hardh, &np);
		hardh = nexth;
	}
	return hardh;
}

static void
link_in(nh_t nh)
{
	node_t *np;
	xfs_ino_t ino;
	gen_t gen;
	nh_t hardh;

#ifdef TREE_DEBUG
	mlog(MLOG_DEBUG | MLOG_TREE,
	    "link_in(%llu): map in node\n", nh);
#endif

	/* map in the node to read ino and gen
	 */
	np = Node_map(nh);
	ino = np->n_ino;
	gen = np->n_gen;

	/* see if one or more links already hashed.
	 */
	hardh = hash_find(ino, gen);

	/* if not hashed, just hash it. otherwise put at end
	 * of hard link (lnk) list.
	 */
	if (hardh == NH_NULL) {
#ifdef TREE_DEBUG
		mlog(MLOG_DEBUG | MLOG_TREE,
		    "link_in(): hash node in for ino %llu\n", ino);
#endif
		hash_in(nh);
	} else {
		nh_t prevh = hardh;
		node_t *prevp = Node_map(prevh);
#ifdef TREE_DEBUG
		mlog(MLOG_DEBUG | MLOG_TREE,
		    "link_in(): put at end of hash list\n");
#endif
		while (prevp->n_lnkh != NH_NULL) {
			nh_t nexth = prevp->n_lnkh;
			Node_unmap(prevh, &prevp);
			prevh = nexth;
			prevp = Node_map(prevh);
		}
		prevp->n_lnkh = nh;
		Node_unmap(prevh, &prevp);
	}

	/* since always put at end of hard link list, make node's
	 * lnk member terminate list.
	 */
	np->n_lnkh = NH_NULL;
	Node_unmap(nh, &np);
#ifdef TREE_DEBUG
	mlog(MLOG_DEBUG | MLOG_TREE,
	    "link_in(%llu): UNmap in node\n", nh);
#endif
}

static void
link_out(nh_t nh)
{
	node_t *np;
	xfs_ino_t ino;
	gen_t gen;
	nh_t hardh;

	/* map in the node to read ino and gen
	 */
	np = Node_map(nh);
	ino = np->n_ino;
	gen = np->n_gen;

	/* get head of hard link list
	 */
	hardh = hash_find(ino, gen);
	assert(hardh != NH_NULL);

	/* if node is at head of hard link list, hash it out and
	 * hash in the following node in link list, if there is one.
	 * otherwise, unlink from hardlink list.
	 */
	if (nh == hardh) {
		hash_out(nh);
		if (np->n_lnkh != NH_NULL) {
			hash_in(np->n_lnkh);
		}
	} else {
		nh_t prevh = hardh;
		node_t *prevp = Node_map(prevh);
		while (prevp->n_lnkh != nh) {
			nh_t nexth = prevp->n_lnkh;
			Node_unmap(prevh, &prevp);
			prevh = nexth;
			assert(prevh != NH_NULL);
			prevp = Node_map(prevh);
		}
		prevp->n_lnkh = np->n_lnkh;
		Node_unmap(prevh, &prevp);
	}
	np->n_lnkh = NH_NULL;

	/* release the mapping
	 */
	Node_unmap(nh, &np);
}

/* invokes callback for all hardheads
 * iteration aborted if callback returns FALSE
 */
static void
link_headiter(bool_t (*cbfp)(void *contextp, nh_t hardh), void *contextp)
{
	hash_iter(cbfp, contextp);
}

/* iterator for a hard link list. allows deletion of the last node
 * returned.
 */
static void
link_iter_init(link_iter_context_t *link_iter_contextp, nh_t hardheadh)
{
	link_iter_contextp->li_headh = hardheadh;
	link_iter_contextp->li_prevh = NH_NULL;
	link_iter_contextp->li_lasth = NH_NULL;
	link_iter_contextp->li_donepr = BOOL_FALSE;
}

static nh_t
link_iter_next(link_iter_context_t *link_iter_contextp)
{
	node_t *lastp;
	nh_t tmplasth;

	/* if already done, return
	 */
	if (link_iter_contextp->li_donepr == BOOL_TRUE) {
		return NH_NULL;
	}

	/* if no hardhead, done
	 */
	if (link_iter_contextp->li_headh == NH_NULL) {
		link_iter_contextp->li_donepr = BOOL_TRUE;
		return NH_NULL;
	}

	/* make tmp copy of last
	 */
	tmplasth = link_iter_contextp->li_lasth;

	/* if no last, must be first call
	 */
	if (tmplasth == NH_NULL) {
		assert(link_iter_contextp->li_prevh == NH_NULL);
		link_iter_contextp->li_lasth = link_iter_contextp->li_headh;
		return link_iter_contextp->li_lasth;
	}

	/* slide last into prev
	 */
	link_iter_contextp->li_prevh = tmplasth;
	lastp = Node_map(tmplasth);
	link_iter_contextp->li_lasth = lastp->n_lnkh;
	Node_unmap(tmplasth, &lastp);

	/* if NULL, flag done
	 */
	if (link_iter_contextp->li_lasth == NH_NULL) {
		link_iter_contextp->li_donepr = BOOL_TRUE;
	}

	/* return the last handle
	 */
	return link_iter_contextp->li_lasth;
}

/* ARGSUSED */
void
link_iter_unlink(link_iter_context_t *link_iter_contextp, nh_t nh)
{
	node_t *lastp;
	nh_t nexth;

	/* sanity checks
	 */
	assert(link_iter_contextp->li_lasth != NH_NULL);
	assert(nh == link_iter_contextp->li_lasth);

	/* get the next node in list
	 */
	lastp = Node_map(link_iter_contextp->li_lasth);
	nexth = lastp->n_lnkh;
	lastp->n_lnkh = NH_NULL;
	Node_unmap(link_iter_contextp->li_lasth, &lastp);

	if (link_iter_contextp->li_lasth == link_iter_contextp->li_headh) {
		assert(link_iter_contextp->li_prevh == NH_NULL);
		hash_out(link_iter_contextp->li_headh);
		link_iter_contextp->li_headh = nexth;
		if (nexth != NH_NULL) {
			hash_in(nexth);
		}
	} else {
		node_t *prevp;
		assert(link_iter_contextp->li_prevh != NH_NULL);
		prevp = Node_map(link_iter_contextp->li_prevh);
		prevp->n_lnkh = nexth;
		Node_unmap(link_iter_contextp->li_prevh, &prevp);
	}
	link_iter_contextp->li_lasth = link_iter_contextp->li_prevh;
	link_iter_contextp->li_prevh = NH_NULL;
}


/* hash abstraction *********************************************************/

#define HASHLEN_MIN	(pgsz / sizeof(nh_t))

static bool_t
hash_init(size64_t vmsz,
	   size64_t dircnt,
	   size64_t nondircnt,
	   char *perspath)
{
	size64_t hashlen;
	size64_t loghashlen;
	size64_t vmlen;
	size64_t hashlenmax;
	ix_t hix;

	/* sanity checks
	 */
	assert(pgsz % sizeof(nh_t) == 0);

	/* calculate the size of the hash array. must be a power of two,
	 * and a multiple of the page size. don't use more than the available
	 * vm. but enforce a minimum.
	 */
	vmlen = vmsz / sizeof(nh_t);
	hashlenmax = min(vmlen, SIZEMAX);
	hashlen = (dircnt + nondircnt);
	hashlen = max(hashlen, (size64_t)HASHLEN_MIN);
	hashlen = min(hashlen, hashlenmax);

	for (loghashlen = 0
	      ;
	      ((size64_t)1 << loghashlen) <= hashlen
	      ;
	      loghashlen++)
		;
	assert(loghashlen > 0);
	hashlen = (size64_t)1 << loghashlen;
	if (hashlen > hashlenmax)
		hashlen >>= 1;
	assert(hashlen <= hashlenmax);

	/* record hash size in persistent state
	 */
	persp->p_hashsz = hashlen * sizeof(nh_t);

	/* map the hash array just after the persistent state header
	 */
	assert(persp->p_hashsz <= SIZEMAX);
	assert(!(persp->p_hashsz % (size64_t)pgsz));
	assert(!(PERSSZ % pgsz));
	tranp->t_hashp = (nh_t *) mmap_autogrow(
					    (size_t)persp->p_hashsz,
					    tranp->t_persfd,
					    (off64_t)PERSSZ);
	if (tranp->t_hashp == (nh_t *)-1) {
		mlog(MLOG_NORMAL | MLOG_TREE, _(
		      "unable to mmap hash array into %s: %s\n"),
		      perspath,
		      strerror(errno));
		return BOOL_FALSE;
	}

	/* initialize the hash array to all NULL node handles
	 */
	for (hix = 0; hix < (ix_t)hashlen; hix++) {
		tranp->t_hashp[hix] = NH_NULL;
	}

	/* build a hash mask. this works because hashlen is a power of two.
	 * record in persistent state.
	 */
	assert(hashlen - 1 <= SIZEMAX);
	persp->p_hashmask = (size_t)(hashlen - 1);

	return BOOL_TRUE;
}

static bool_t
hash_sync(char *perspath)
{
	size64_t hashsz;

	/* sanity checks
	 */
	assert(pgsz % sizeof(nh_t) == 0);

	/* retrieve the hash size from the persistent state
	 */
	hashsz = persp->p_hashsz;
	assert(!(hashsz % sizeof(nh_t)));

	/* map the hash array just after the persistent state header
	 */
	assert(hashsz <= SIZEMAX);
	assert(!(hashsz % (size64_t)pgsz));
	assert(!(PERSSZ % pgsz));
	tranp->t_hashp = (nh_t *) mmap_autogrow(
					    (size_t)hashsz,
					    tranp->t_persfd,
					    (off64_t)PERSSZ);
	if (tranp->t_hashp == (nh_t *)-1) {
		mlog(MLOG_NORMAL | MLOG_TREE, _(
		      "unable to mmap hash array into %s: %s\n"),
		      perspath,
		      strerror(errno));
		return BOOL_FALSE;
	}

	return BOOL_TRUE;
}

static inline size_t
hash_val(xfs_ino_t ino, size_t hashmask)
{
	ino += ~(ino << 15);
	ino ^=  (ino >> 10);
	ino +=  (ino << 3);
	ino ^=  (ino >> 6);
	ino += ~(ino << 11);
	ino ^=  (ino >> 16);
	return (size_t)ino & hashmask;
}

static void
hash_in(nh_t nh)
{
	node_t *np;
	xfs_ino_t ino;
	size_t hix;
	nh_t *entryp;

	/* get a mapping to the node
	 */
	np = Node_map(nh);

	/* get ino from node
	 */
	ino = np->n_ino;

	/* assert not already in
	 */
	assert(hash_find(np->n_ino, np->n_gen) == NH_NULL);

	/* calculate the hash index
	 */
	hix = hash_val(ino, persp->p_hashmask);

	/* get a pointer to the indexed hash array entry
	 */
	entryp = &tranp->t_hashp[hix];

	/* insert into the list, at the head
	 */
	assert(np->n_hashh == NH_NULL);
	np->n_hashh = *entryp;
	*entryp = nh;

	/* release the mapping
	 */
	Node_unmap(nh, &np);
}

static void
hash_out(nh_t nh)
{
	node_t *np;
	xfs_ino_t ino;
	nh_t hashheadh;
	size_t hix;
	nh_t *entryp;

	/* get a mapping to the node
	 */
	np = Node_map(nh);

	/* get the ino
	 */
	ino = np->n_ino;

	/* get a pointer to the hash array entry
	 */
	hix = hash_val(ino, persp->p_hashmask);
	entryp = &tranp->t_hashp[hix];

	/* get the handle of the first node in the appropriate hash array
	 */
	hashheadh = *entryp;
	assert(hashheadh != NH_NULL);

	/* if node is first in list, replace entry with following node.
	 * otherwise, walk the list until found.
	 */
	if (hashheadh == nh) {
		*entryp = np->n_hashh;
	} else {
		nh_t prevh = hashheadh;
		node_t *prevp = Node_map(prevh);
		while (prevp->n_hashh != nh) {
			nh_t nexth = prevp->n_hashh;
			Node_unmap(prevh, &prevp);
			prevh = nexth;
			assert(prevh != NH_NULL);
			prevp = Node_map(prevh);
		}
		prevp->n_hashh = np->n_hashh;
		Node_unmap(prevh, &prevp);
	}
	np->n_hashh = NH_NULL;

	/* release the mapping
	 */
	Node_unmap(nh, &np);
}

static nh_t
hash_find(xfs_ino_t ino, gen_t gen)
{
	nh_t nh;
	node_t *np;
	size_t hix;

	/* get handle to first node in appropriate hash array
	 */
	hix = hash_val(ino, persp->p_hashmask);
	nh = tranp->t_hashp[hix];

	/* if list empty, return null handle
	 */
	if (nh == NH_NULL) {
		return NH_NULL;
	}

#ifdef TREE_DEBUG
	mlog(MLOG_DEBUG | MLOG_TREE,
	     "hash_find(%llu,%u): traversing hash link list\n",
		ino, gen);
#endif

	/* walk the list until found.
	 */
	np = Node_map(nh);
	while (np->n_ino != ino || np->n_gen != gen) {
		nh_t nextnh = np->n_hashh;
		Node_unmap(nh, &np);
		nh = nextnh;
		if (nh == NH_NULL) {
			return NH_NULL;
		}
		np = Node_map(nh);
	}
	Node_unmap(nh, &np);

#ifdef TREE_DEBUG
	mlog(MLOG_DEBUG | MLOG_TREE,
	    "hash_find(): return nh = %llu\n", nh);
#endif
	return nh;
}

/* invokes callback for all hashed nodes
 * iteration aborted if callback returns FALSE
 * call back may hash out and free the node, so
 * must figure next node prior to calling callback.
 */
static void
hash_iter(bool_t (*cbfp)(void *contextp, nh_t hashh), void *contextp)
{
	ix_t hix;
	size64_t hashlen = persp->p_hashsz / sizeof(nh_t);

	for (hix = 0; hix < (ix_t)hashlen; hix++) {
		nh_t nh = tranp->t_hashp[hix];

		while (nh != NH_NULL) {
			node_t *np;
			nh_t nexth;
			bool_t ok;

			np = Node_map(nh);
			nexth = np->n_hashh;
			Node_unmap(nh, &np);

			ok = (*cbfp)(contextp, nh);
			if (!ok) {
				return;
			}

			nh = nexth;
		}
	}
}

/* misc static functions *****************************************************/

#ifdef TREE_CHK
/* use hash array to iterate through all nodes. check
 * each node's hash, hardlink, namreg, dirattr, parent,
 * and sibling handles.
 */
static bool_t
Node_chk(nh_t nh, nh_t *nexthashhp, nh_t *nextlnkhp)
{
	node_t *np;
	node_t n;
	char nambuf[NAME_MAX + 1];
	bool_t okaccum;

	mlog(MLOG_NITTY + 1 | MLOG_TREE,
	      "checking node nh == 0x%x\n",
	      nh);

	okaccum = BOOL_TRUE;

	if (nexthashhp) {
		*nexthashhp = NH_NULL;
	}

	assert(nextlnkhp);
	*nextlnkhp = NH_NULL;

	np = Node_map(nh);
	assert(np);
	n = *np;
	Node_unmap(nh, &np);

	if (!nexthashhp && n.n_hashh != NH_NULL) {
		mlog(MLOG_NORMAL | MLOG_ERROR | MLOG_TREE, _(
		      "nh 0x%x np 0x%x hash link not null\n"),
		      nh,
		      np);
		okaccum = BOOL_FALSE;
	}

	if (n.n_hashh != NH_NULL) {
		np = Node_map(n.n_hashh);
		Node_unmap(n.n_hashh, &np);
	}

	if (n.n_lnkh != NH_NULL) {
		np = Node_map(n.n_lnkh);
		Node_unmap(n.n_lnkh, &np);
	}

	if (n.n_parh != NH_NULL) {
		np = Node_map(n.n_parh);
		Node_unmap(n.n_parh, &np);
	}

	if (n.n_cldh != NH_NULL) {
		np = Node_map(n.n_cldh);
		Node_unmap(n.n_cldh, &np);
	}

	if (n.n_sibh != NH_NULL) {
		np = Node_map(n.n_sibh);
		Node_unmap(n.n_sibh, &np);
	}

	if (n.n_nrh != NRH_NULL) {
		int rval;
		rval = namreg_get(n.n_nrh, nambuf, sizeof(nambuf));
		assert(rval >= 0);
	}

	if (n.n_dah != DAH_NULL) {
		(void)dirattr_get_mode(n.n_dah);
	}

	if (nexthashhp) {
		*nexthashhp = n.n_hashh;
	}

	*nextlnkhp = n.n_lnkh;

	return okaccum;
}

bool_t
tree_chk(void)
{
	ix_t hix;
	size64_t hashlen = persp->p_hashsz / sizeof(nh_t);
	bool_t ok;
	bool_t okaccum;

	okaccum = BOOL_TRUE;

	for (hix = 0; hix < (ix_t)hashlen; hix++) {
		nh_t hashh = tranp->t_hashp[hix];

		mlog(MLOG_NITTY + 1 | MLOG_TREE,
		      "checking hix %u\n",
		      hix);
		while (hashh != NH_NULL) {
			nh_t lnkh;

			ok = Node_chk(hashh, &hashh, &lnkh);
			if (!ok) {
				okaccum = BOOL_FALSE;
			}

			while (lnkh != NH_NULL) {
				ok = Node_chk(lnkh, 0, &lnkh);
				if (!ok) {
					okaccum = BOOL_FALSE;
				}
			}
		}
	}

	ok = tree_chk2();
	if (!ok) {
		okaccum = BOOL_FALSE;
	}

	return okaccum;
}

static bool_t tree_chk2_recurse(nh_t cldh, nh_t parh);

static bool_t
tree_chk2(void)
{
	node_t *rootp;
	nh_t cldh;
	bool_t ok;

	mlog(MLOG_DEBUG | MLOG_TREE,
	      "tree hierarchy check\n");

	rootp = Node_map(persp->p_rooth);
	cldh = rootp->n_cldh;
	Node_unmap(persp->p_rooth, &rootp);

	ok = tree_chk2_recurse(cldh, persp->p_rooth);

	return ok;
}

static bool_t
tree_chk2_recurse(nh_t cldh, nh_t parh)
{
	bool_t okaccum = BOOL_TRUE;

	assert(parh != NH_NULL);

	while (cldh != NH_NULL) {
		node_t *cldp;
		xfs_ino_t ino;
		gen_t gen;
		nh_t nodeparh;
		nrh_t nrh;
		nh_t grandcldh;
		nh_t nextcldh;
		bool_t ok;

		cldp = Node_map(cldh);
		ino = cldp->n_ino;
		gen = cldp->n_gen;
		nodeparh = cldp->n_parh;
		nrh = cldp->n_nrh;
		grandcldh = cldp->n_cldh;
		nextcldh = cldp->n_sibh;
		Node_unmap(cldh, &cldp);

		if (parh == persp->p_orphh) {
			sprintf(tranp->t_namebuf, "%llu.%u", ino, gen);
		} else if (cldh == persp->p_orphh) {
			sprintf(tranp->t_namebuf, "%llu.%u", ino, gen);
		} else {
			int namelen;
			namelen = namreg_get(nrh,
					      tranp->t_namebuf,
					      sizeof(tranp->t_namebuf));
			assert(namelen >= 0);
		}

		if (nodeparh == NH_NULL) {
			mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_TREE, _(
			      "node %x %s %llu %u parent NULL\n"),
			      cldh,
			      tranp->t_namebuf,
			      ino,
			      gen);
			return BOOL_FALSE;
		} else if (nodeparh != parh) {
			mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_TREE, _(
			      "node %x %s %llu %u parent mismatch: "
			      "nodepar %x par %x\n"),
			      cldh,
			      tranp->t_namebuf,
			      ino,
			      gen,
			      nodeparh,
			      parh);
			return BOOL_FALSE;
		} else {
			mlog(MLOG_DEBUG | MLOG_TREE,
			      "node %x %s %llu %u  parent %x\n",
			      cldh,
			      tranp->t_namebuf,
			      ino,
			      gen,
			      parh);
		}
		ok = tree_chk2_recurse(grandcldh, cldh);
		if (!ok) {
			okaccum = BOOL_FALSE;
		}

		cldh = nextcldh;
	}

	return okaccum;
}

#endif /* TREE_CHK */

static char *whites = " \t\r\n\v\f";

static int is_white(char c);
static void fix_escape(char *string, char *liter);
static void fix_quoted_span(char *string, char *liter);
static void collapse_white(char *string, char *liter);
static size_t distance_to_space(char *s, char *l);

static int
parse(int slotcnt, char **slotbuf, char *string)
{
	char *liter;
	char *s;
	char *l;
	size_t wordcnt;

	/* sanity checkcs
	 */
	assert(slotcnt >= 0);

	/* allocate a companion to the input string for identifying
	 * characters which are to be interpreted literally.
	 */
	liter = (char *)calloc(1, strlen(string) + 1);
	if (!liter) {
		return -1;
	}

	/* pass 1: collapse escape sequences, identifying characters which
	 * are to be interpreted literally
	 */
	for (s = string, l = liter; *s; s++, l++) {
		if (*s == '\\' && !*l) {
			fix_escape(s, l);
		}
	}

	/* pass 2: collapse quoted spans, identifying characters which
	 * are to be interpreted literally
	 */
	for (s = string, l = liter; *s; s++, l++) {
		if (*s == '\"' && ! *l) {
			fix_quoted_span(s, l);
		}
	}

	/* pass 3: collapse white space spans into a single space
	 */
	for (s = string, l = liter; *s; s++, l++) {
		if (is_white(*s) && !*l) {
			collapse_white(s, l);
		}
	}

	/* pass 4: identify and null-terminate words
	 */
	wordcnt = 0;
	s = string;
	l = liter;
	while (*s) {
		size_t d;
		if (wordcnt < (size_t)slotcnt) {
			slotbuf[wordcnt] = s;
		}
		wordcnt++;
		d = distance_to_space(s, l);
		s += d;
		l += d;
		if (*s) {
			*s = 0;
			s++;
			l++;
		}
	}

	free((void *)liter);
	return (int)wordcnt;
}

struct escape_table {
	char sequence;
	char substitute;
};
typedef struct escape_table escape_table_t;

static escape_table_t escape_table[] = {
 {	'n',	'\n' },
 {	't',	'\t' },
 {	'v',	'\v' },
 {	'b',	'\b' },
 {	'r',	'\r' },
 {	'f',	'\f' },
 {	'f',	'\f' },
 {	'a',	'\a' },
 {	'\\',	'\\' }
};

static void shrink(char *s, size_t cnt);
static int is_hex(char c);
static size_t hex_to_size(char c);
static int is_octal(char c);
static size_t octal_to_size(char c);

static void
fix_escape(char *string, char *liter)
{
	escape_table_t *ep;
	escape_table_t *endep;

	/* first look for special escapes described in table
	 */
	ep = escape_table;
	endep = escape_table + (sizeof(escape_table)
			         /
			         sizeof(escape_table[0]));
	for (; ep < endep; ep++) {
		if (string[1] == ep->sequence) {
			string[0] = ep->substitute;
			liter[0] = (char)1;
			shrink(&string[1], 1);
			shrink(&liter[1], 1);
			return;
		}
	}

	/* detect white space escapes
	 */
	if (is_white(string[1])) {
		liter[0] = (char)1;
		shrink(&string[0], 1);
		shrink(&liter[1], 1);
		return;
	}

	/* detect hex escapes (don't allow null)
	 */
	if (string[1] == 'x') {
		size_t hexlen;
		size_t accum;
		accum = 0;
		for (hexlen = 0
		      ;
		      hexlen < 2 && is_hex(string[2 + hexlen])
		      ;
		      hexlen++) {
			accum *= 16;
			accum += hex_to_size(string[2 + hexlen]);
		}
		if (hexlen && accum) {
			string[0] = (char)accum;
			liter[0] = (char)1;
			shrink(&string[1], 1 + hexlen);
			shrink(&liter[1], 1 + hexlen);
			return;
		}
	}

	/* detect octal escapes (don't allow null)
	 */
	if (is_octal(string[1])) {
		size_t octallen;
		size_t accum;
		accum = octal_to_size(string[1]);
		for (octallen = 1
		      ;
		      octallen < 3 && is_octal(string[1 + octallen])
		      ;
		      octallen++) {
			accum *= 8;
			accum += octal_to_size(string[1 + octallen]);
		}
		if (accum) {
			string[0] = (char)accum;
			liter[0] = (char)1;
			shrink(&string[1], octallen);
			shrink(&liter[1], octallen);
			return;
		}
	}

	/* didn't match any escape sequences, so assume literal
	 */
	liter[0] = (char)1;
}

static void
fix_quoted_span(char *string, char *liter)
{
	char *s;
	char *l;

	/* first cover the leading quote
	 */
	shrink(string, 1);
	shrink(liter, 1);

	/* scan for the next non-literal quote, marking all
	 * characters in between as literal
	 */
	for (s = string, l = liter; *s && (*s != '\"' || *l); s++, l++) {
		*l = (char)1;
	}

	if (*s) {
		shrink(s, 1);
		shrink(l, 1);
	}
}

static void
collapse_white(char *string, char *liter)
{
	char *s;
	char *l;
	size_t cnt;

	cnt = 0;
	for (s = string, l = liter; is_white(*s) && !*l; s++, l++) {
		cnt++;
	}

	string[0] = ' ';

	if (cnt > 1) {
		shrink(&string[1], cnt - 1);
		shrink(&liter[1], cnt - 1);
	}
}

static size_t
distance_to_space(char *s, char *l)
{
	size_t cnt;

	for (cnt = 0; *s && (!is_white(*s) || *l); s++, l++) {
		cnt++;
	}

	return cnt;
}

static void
shrink(char *s, size_t cnt)
{
	strcpy(s, s + cnt);
}

static int
is_white(char c)
{
	if (c && strchr(whites, (int)c)) {
		return 1;
	} else {
		return 0;
	}
}

static int
is_hex(char c)
{
	if (c >= '0' && c <= '9') {
		return 1;
	}

	if (c >= 'a' && c <= 'f') {
		return 1;
	}

	if (c >= 'A' && c <= 'F') {
		return 1;
	}

	return 0;
}

static size_t
hex_to_size(char c)
{
	if (c >= '0' && c <= '9') {
		return (size_t)(c - '0');
	}

	if (c >= 'a' && c <= 'f') {
		return (size_t)(c - 'a') + (size_t)0xa;
	}

	if (c >= 'A' && c <= 'F') {
		return (size_t)(c - 'A') + (size_t)0xa;
	}

	return 0;
}

static int
is_octal(char c)
{
	if (c >= '0' && c <= '7') {
		return 1;
	}

	return 0;
}

static size_t
octal_to_size(char c)
{
	if (c >= '0' && c <= '7') {
		return (size_t)(c - '0');
	}

	return 0;
}

static int
mkdir_r(char *path)
{
	struct stat sbuf;
	char *path_copy;
	int ret;

	if (stat(path, &sbuf) < 0) {
		path_copy = strdup(path);
		if (!path_copy) {
			mlog(MLOG_TRACE | MLOG_ERROR | MLOG_TREE,
				_("unable to allocate memory for a path\n"));
			mlog_exit(EXIT_ERROR, RV_ERROR);
			exit(1);
		}

		ret = mkdir_r(dirname(path_copy));
		free(path_copy);

		if (ret < 0)
			return -1;
		return mkdir(path, 0755);
	}
	else if ((sbuf.st_mode & S_IFDIR) == 0) {
		mlog(MLOG_TRACE | MLOG_ERROR | MLOG_TREE, _(
			"%s is not a directory\n"),
			path);
		mlog_exit(EXIT_ERROR, RV_EXISTS);
		exit(1);
	}
	return 0;
}
