[BACK]Return to tree.c CVS log [TXT][DIR] Up to [Development] / xfs-cmds / xfsdump / restore

File: [Development] / xfs-cmds / xfsdump / restore / tree.c (download)

Revision 1.19, Mon Jan 5 17:08:20 2004 UTC (13 years, 9 months ago) by alkirkco
Branch: MAIN
Changes since 1.18: +43 -4 lines

Add support to properly dump and restore the new inode flags:
immutable, append, sync, noatime and nodump.  Add all inode
flags, new and old, to the list defined in _mk_fillconfig_ea()
of cmd/xfstests/common.dump.  Test 063 can be used to verify
the proper backup and restore of these flags.  Also update man
pages to 1) remove reference to miniroot (IRIX-only), 2) document
the new XFS_XFLAG_NODUMP inode flag as the preferred method to
exclude files, and 3) better describe how the media inventory
files can be used.

The code for the inode flags was contributed by Ethan Benson,
with copyright assignment obtained by Nathan Scott.
Add code to restore xflags from an inode.

/*
 * 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 version 2 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.
 * 
 * Further, this software is distributed without any warranty that it is
 * free of the rightful claim of any third person regarding infringement
 * or the like.  Any license provided herein, whether implied or
 * otherwise, applies only to this software file.  Patent licenses, if
 * any, provided herein do not apply to combinations of this program with
 * other software, or any other product whatsoever.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write the Free Software Foundation, Inc., 59
 * Temple Place - Suite 330, Boston MA 02111-1307, USA.
 * 
 * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy,
 * Mountain View, CA  94043, or:
 * 
 * http://www.sgi.com 
 * 
 * For further information regarding this notice, see: 
 * 
 * http://oss.sgi.com/projects/GenInfo/SGIGPLNoticeExplan/
 */

#include <xfs/libxfs.h>
#include <xfs/jdm.h>

#include <sys/mman.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 "types.h"
#include "exit.h"
#include "util.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 "win.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 eigth, rest goes to node array
 */
#define HASHSZ_PERVM	8


/* 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_restoredmpr;
		/* restore DMI event settings
		 */
};

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
		 */
	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.
		 */
	intgen_t 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	48

struct node {
	xfs_ino_t n_ino;		/* 8  8 ino */
	nrh_t n_nrh;		/* 4 12 handle to name in name registry */
	dah_t n_dah;		/* 4 16 handle to directory attributes */
	nh_t n_hashh;		/* 4 20 hash array */
	nh_t n_parh;		/* 4 24 parent */
	nh_t n_sibh;		/* 4 28 sibling list */
	nh_t n_sibprevh;	/* 4 32 prev sibling list - dbl link list */
	nh_t n_cldh;		/* 4 36 children list */
	nh_t n_lnkh;		/* 4 40 hard link list */
	gen_t n_gen;		/* 2 42 generation count mod 0x10000 */
	u_char_t n_flags;	/* 1 43 action and state flags */
	u_char_t n_nodehkbyte;	/* 1 44 given to node abstraction */
	int32_t pad;		/* 4 48 padding to 8 byte boundary */
};

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;


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

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

/* 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 intgen_t Node2path_recurse( nh_t nh, char *buf, intgen_t bufsz );
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 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;


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

/* 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 restoredmpr,
	   bool_t largewindowpr )
{
	off64_t nodeoff;
	char *perspath;
	bool_t ok;
	intgen_t 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;

	/* 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,
				      strerror( errno ));
			} 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, largewindowpr );
	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.
	 */
	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 DMI event settings should be restored
	 */
	persp->p_restoredmpr = restoredmpr;

	return BOOL_TRUE;
}

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

#ifdef SESSCPLT
	if ( persp ) {
		return BOOL_TRUE;
	}
#endif /* SESSCPLT */

	/* 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;

	/* 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;

	/* 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;
}

/* 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;
	u_int32_t biggen = fhdrp->fh_stat.bs_gen;
	gen_t gen = BIGGEN2GEN( biggen );
	dah_t dah;

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

	/* lookup head of hardlink list
	 */
	hardh = link_hardh( ino, gen );
	ASSERT( ino != persp->p_rootino || hardh == persp->p_rooth );

	/* 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,
			      biggen );
			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,
			      biggen );
			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,
			      biggen );
		}
		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,
		      biggen );
		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, size_t g, char *name, size_t namelen )
{
	gen_t gen = BIGGEN2GEN( g );
	nh_t hardh;

	/* 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 */
			intgen_t 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 );
	}
	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 isrealpr;
		bool_t isrefpr;
		bool_t isrenamepr;
		nh_t renameh;
		nh_t grandcldh;
		nh_t nextcldh;
		intgen_t 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 );
		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 ( 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 ) {
			intgen_t 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;
			intgen_t 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,
	       u_int32_t biggen,
	       int32_t ctime,
	       int32_t mtime,
	       bool_t ( * funcp )( void *contextp,
				   bool_t linkpr,
				   char *path1,
				   char *path2 ),
	       void *contextp,
	       char *path1,
	       char *path2 )
{
	gen_t gen = BIGGEN2GEN( biggen );
	nh_t hardh;
	nh_t nh;
	char *path;
	bool_t ok;
	int  rval;

	/* 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 )) {
			if ( ! content_overwrite_ok( path,
						     ctime,
						     mtime,
						     &reasonstr )) {
				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 ) {
					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 {
				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,
			      biggen );
			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 = ( intgen_t )cldp->n_flags & NF_DUMPEDDIR;
		cldrefedpr = ( intgen_t )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;
	}
}

#ifdef EXTATTR
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;
}
#endif /* EXTATTR */

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;
	intgen_t 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 && ! isselpr)
		     ||
		     (! isrealpr && ! isrefpr &&   isselpr) ) {
			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)
		       ||
		       (isrealpr &&   isrefpr && ! isselpr)
		       ||
		       (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;
	/* REFERENCED */
	struct fsxattr fsxattr;
	intgen_t rval;
	size_t	hlen;
	void	*hanp;
	intgen_t fd;

	if ( dah == DAH_NULL )
		return;

	fsxattr.fsx_xflags = dirattr_get_xflags( dah );
	fsxattr.fsx_extsize = dirattr_get_extsize( dah );

#ifdef DMEXTATTR
	if ( persp->p_restoredmpr ) {
		fsdmidata_t fssetdm;

		fssetdm.fsd_dmevmask = dirattr_get_dmevmask( dah );
		fssetdm.fsd_padding = 0;	/* not used */
		fssetdm.fsd_dmstate = ( u_int16_t )dirattr_get_dmstate( dah );

		/* restore DMAPI event settings etc.
		 */
		if (path_to_handle(path, &hanp, &hlen)) {
			mlog( MLOG_NORMAL | MLOG_WARNING, _(
				"path_to_handle of %s failed:%s\n"),
				path, strerror( errno ));
			return;
		}
		
		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);
			return;
		}
		free_handle(hanp, hlen);
		rval = ioctl( fd,
			      XFS_IOC_FSSETDM,
			      ( void * )&fssetdm );
		if ( rval ) {
			mlog( errno == EINVAL
			      ?
			      ( MLOG_NITTY + 1 ) | MLOG_TREE
			      :
			      MLOG_NITTY | MLOG_TREE,
			      "set DMI attributes"
			      " of %s failed: %s\n",
			      path,
			      strerror( errno ));
		}
		( void )close( fd );
	}
#endif /* DMEXTATTR */

	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 ));
	}

#ifdef EXTATTR
	/* set the extended attributes
	 */
	if (path_to_handle(path, &hanp, &hlen)) {
		mlog( MLOG_NORMAL | MLOG_WARNING, _(
			      "path_to_handle of %s failed:%s\n"),
		      path, strerror( errno ));
		return;
	}
		
	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);
		return;
	}
	free_handle(hanp, hlen);
	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)"
		       "of %s failed: "
		       "%s\n"),
		     dirattr_get_xflags( dah ),
		     dirattr_get_extsize( dah ),
		     path,
		     strerror(errno));
	}
	( void )close( fd );
#endif /* EXTATTR */

}

/* deletes orphanage if empty, else warns
 */
bool_t
tree_delorph( void )
{
	intgen_t 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 intgen_t 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 */
			intgen_t 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 )
{
}

#ifdef WHITEPARSE

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 );
}

#else /* WHITEPARSE */

static void
tsi_cmd_parse( char *buf )
{
	size_t argc;
	char *t;
	char *b;

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

	argc = 0;
	b = buf;
	while ( argc < INTER_ARGMAX && ( t = strtok( b, " \t" )) != 0 ) {
		tranp->t_inter.i_argv[ argc++ ] = t;
		b = 0;
	}
	tranp->t_inter.i_argc = argc;
}

#endif /* WHITEPARSE */

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 */
			intgen_t 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;
	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 != NRH_NULL ) {
		dirattr_del( np->n_dah );
		np->n_dah = NRH_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 )
{
	intgen_t remainingcnt;
	path[ 0 ] = 0; /* in case root node passed in */
	remainingcnt = Node2path_recurse( nh, path, MAXPATHLEN );
	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 intgen_t
Node2path_recurse( nh_t nh, char *buf, intgen_t bufsz )
{
	node_t *np;
	nh_t parh;
	xfs_ino_t ino;
	gen_t gen;
	nrh_t nrh;
	intgen_t oldbufsz;
	intgen_t namelen;

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

	/* 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
	 */
	oldbufsz = bufsz;
	bufsz = Node2path_recurse( parh, buf, bufsz ); /* 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 );
	}

	/* return remaining buffer size
	 */
	bufsz -= namelen;
	ASSERT( bufsz + MAXPATHLEN > 0 );
	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 )
{
	return hash_find( ino, gen );
}

/* 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 */
			intgen_t 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 );
	loghashlen--;
	hashlen = ( size64_t )1 << loghashlen;
	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 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 = ( size_t )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 = ( size_t )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 = ( size_t )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 ) {
		intgen_t 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 {
			intgen_t 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 */

#ifdef WHITEPARSE

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;
}

#endif /* WHITEPARSE */

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

	if (stat(path, &sbuf) < 0) {
		if (mkdir_r(dirname(strdup(path))) < 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;
}