| /* |
| * Copyright (c) 2000-2002 Silicon Graphics, Inc. |
| * All Rights Reserved. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it would be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <sys/mman.h> |
| #include <sys/types.h> |
| #include <sys/param.h> |
| #include <fcntl.h> |
| #include <sys/stat.h> |
| #include <sys/ioctl.h> |
| #include <utime.h> |
| #include <limits.h> |
| #include <time.h> |
| #include <xfs/handle.h> |
| #include <dirent.h> |
| #include <sys/ioctl.h> |
| #include <assert.h> |
| #include <string.h> |
| #include <uuid/uuid.h> |
| #include <xfs/xfs.h> |
| |
| #include "config.h" |
| |
| #include "types.h" |
| #include "exit.h" |
| #include "cldmgr.h" |
| #include "path.h" |
| #include "openutil.h" |
| #include "getopt.h" |
| #include "stream.h" |
| #include "mlog.h" |
| #include "dlog.h" |
| #include "global.h" |
| #include "drive.h" |
| #include "media.h" |
| #include "content.h" |
| #include "content_inode.h" |
| #include "inomap.h" |
| #include "namreg.h" |
| #include "dirattr.h" |
| #include "bag.h" |
| #include "node.h" |
| #include "tree.h" |
| #include "libgen.h" |
| #include "mmap.h" |
| #include "exit.h" |
| |
| /* structure definitions used locally ****************************************/ |
| |
| /* name of persistent state file |
| */ |
| #define PERS_NAME "tree" |
| |
| /* orphanage specifics. ino must be otherwise unused in the dump source fs. |
| * zero works. |
| */ |
| #define ORPH_INO 0 |
| #define ORPH_NAME "orphanage" |
| |
| |
| /* VM budgeting - give hash array one sixteenth, rest goes to node array |
| */ |
| #define HASHSZ_PERVM 16 |
| |
| /* reserve the first page for persistent state |
| */ |
| struct treePersStorage { |
| xfs_ino_t p_rootino; |
| /* ino of root |
| */ |
| nh_t p_rooth; |
| /* handle of root node |
| */ |
| nh_t p_orphh; |
| /* handle to orphanage node |
| */ |
| size64_t p_hashsz; |
| /* size of hash array (private to hash abstraction) |
| */ |
| size_t p_hashmask; |
| /* hash mask (private to hash abstraction) |
| */ |
| bool_t p_ownerpr; |
| /* restore directory owner/group attributes |
| */ |
| bool_t p_fullpr; |
| /* restoring a full level 0 non-resumed dump (can skip |
| * some steps) |
| */ |
| bool_t p_ignoreorphpr; |
| /* set if positive subtree or interactive |
| */ |
| bool_t p_restoredmpr; |
| /* restore DMI event settings |
| */ |
| bool_t p_truncategenpr; |
| /* truncate inode generation number (for compatibility |
| * with xfsdump format 2 and earlier) |
| */ |
| }; |
| |
| typedef struct treePersStorage treepers_t; |
| |
| #define PERSSZ perssz |
| |
| |
| /* interactive dialog transient state |
| */ |
| #define INTER_ARGMAX 10 /* max number of args to interactive cmds */ |
| struct inter { |
| size_t i_argc; |
| char *i_argv[ INTER_ARGMAX ]; |
| nh_t i_cwdh; |
| char i_name[ NAME_MAX + 1 ]; |
| }; |
| |
| typedef struct inter inter_t; |
| |
| /* transient state |
| */ |
| struct tran { |
| bool_t t_toconlypr; |
| /* just display table of contents; don't restore files |
| */ |
| char *t_hkdir; |
| /* full absolute pathname of housekeeping directory |
| */ |
| char *t_dstdir; |
| /* full absolute pathname of destination directory |
| */ |
| bool_t t_dstdirisxfspr; |
| /* destination directory is an xfs filesystem; xfs-specific |
| * calls can be made when needed. |
| */ |
| char *t_orphdir; |
| /* full absolute pathname of orphanage directory |
| */ |
| char *t_hksubtree; |
| /* if non-NULL, is path of hkdir relative to dstdir. |
| * don't restore there. |
| */ |
| int t_persfd; |
| /* file descriptor of the persistent state file |
| */ |
| nh_t *t_hashp; |
| /* pointer to mapped hash array (private to hash abstraction) |
| */ |
| char t_namebuf[ NAME_MAX + 1 ]; |
| /* to hold names temporarily retrieved from name registry |
| */ |
| inter_t t_inter; |
| /* context for interactive subtree selection |
| */ |
| }; |
| |
| typedef struct tran tran_t; |
| |
| |
| /* node structure. each node represents a directory entry |
| */ |
| #define NODESZ 56 |
| |
| struct node { |
| xfs_ino_t n_ino; /* 8 8 ino */ |
| nrh_t n_nrh; /* 8 16 handle to name in name registry */ |
| dah_t n_dah; /* 4 20 handle to directory attributes */ |
| nh_t n_hashh; /* 4 24 hash array */ |
| nh_t n_parh; /* 4 28 parent */ |
| nh_t n_sibh; /* 4 32 sibling list */ |
| nh_t n_sibprevh; /* 4 36 prev sibling list - dbl link list */ |
| nh_t n_cldh; /* 4 40 children list */ |
| nh_t n_lnkh; /* 4 44 hard link list */ |
| gen_t n_gen; /* 4 48 generation count mod 0x10000 */ |
| u_char_t n_flags; /* 1 49 action and state flags */ |
| u_char_t n_nodehkbyte; /* 1 50 given to node abstraction */ |
| char n_pad[6]; /* 6 56 */ |
| }; |
| |
| typedef struct node node_t; |
| |
| #define NF_REAL ( 1 << 0 ) |
| /* set when the corresponding file/dir has been created in |
| * the restore destination. |
| */ |
| #define NF_SUBTREE ( 1 << 1 ) |
| /* marks nodes in the selected subtrees. |
| */ |
| #define NF_REFED ( 1 << 2 ) |
| /* indicates node is still referenced according to incremental/resumed |
| * dump. used to detect dirents no longer used. prior to restoring |
| * a dump session, this flag is cleared in all nodes. during the dirent |
| * restoral, it is set. it may also be set during the adjustment |
| * for referenced but undumped directories. NOTE: nodes in the |
| * orphanage NEVER have this flag set. |
| */ |
| #define NF_WRITTEN ( 1 << 3 ) |
| /* set as soon as a non-dir node restore is begun. allows |
| * overwrite inhibit options to work with segmented files |
| */ |
| #define NF_ISDIR ( 1 << 4 ) |
| /* indicates this node is a directory. set when a directory is taken |
| * from the dirdump. |
| */ |
| #define NF_DUMPEDDIR ( 1 << 5 ) |
| /* indicates this node is a directory present in the current dirdump. |
| * at beginning of session, this flag is cleared in all nodes. |
| * then as each directory dump is read from media, the flag |
| * is set from the corresponding node. this allows adjustments to |
| * the NF_REFED flag: if a directory was not dumped, either it no |
| * longer exists or it has not changed. if it is referenced, we assume |
| * it exists, in which case if it is not dumped then all of its entries |
| * are referenced as well. |
| */ |
| #define NF_NEWORPH ( 1 << 6 ) |
| /* cleared from all nodes in the orphanage before a dump is applied. |
| * set if a dir is seen in the dirdump but no node exists for it. |
| * cleared if that dir is adopted subsequently during the dirdump. |
| * set if a nondir is seen in the nondir dump but no node exists for |
| * it. in either case a node is created and placed in the orphanage. |
| * during rmdir/unlink processing, nodes so marked are left alone. |
| * since the flag is cleared at the beginning of the next increment, |
| * old orphans had better be adopted, otherwise they will be unlinked. |
| */ |
| |
| /* link list iterator context |
| */ |
| struct link_iter_context { |
| nh_t li_headh; /* head of hard link list */ |
| nh_t li_prevh; /* next to last node returned by _next() */ |
| nh_t li_lasth; /* last node returned by _next() */ |
| bool_t li_donepr; /* set as soon as last.next null */ |
| }; |
| typedef struct link_iter_context link_iter_context_t; |
| |
| /* used for caching parent pathname from previous Node2path result |
| */ |
| struct path_cache { |
| nh_t nh; |
| int len; |
| char buf[MAXPATHLEN]; |
| }; |
| typedef struct path_cache path_cache_t; |
| |
| /* declarations of externally defined global symbols *************************/ |
| |
| extern void usage( void ); |
| extern size_t pgsz; |
| extern size_t pgmask; |
| extern bool_t restore_rootdir_permissions; |
| |
| /* forward declarations of locally defined static functions ******************/ |
| |
| static nh_t Node_alloc( xfs_ino_t ino, |
| gen_t gen, |
| nrh_t nrh, |
| dah_t dah, |
| size_t flags ); |
| static void Node_free( nh_t *nhp ); |
| static node_t * Node_map( nh_t nh ); |
| static void Node_unmap( nh_t nh, node_t **npp ); |
| static int Node2path_recurse( nh_t nh, char *buf, |
| int bufsz, int level ); |
| static void adopt( nh_t parh, nh_t cldh, nrh_t nrh ); |
| static nrh_t disown( nh_t cldh ); |
| static void selsubtree( nh_t nh, bool_t sensepr ); |
| static void selsubtree_recurse_down( nh_t nh, bool_t sensepr ); |
| static nh_t link_hardh( xfs_ino_t ino, gen_t gen ); |
| static nh_t link_nexth( nh_t nh ); |
| static nh_t link_matchh( nh_t hardh, nh_t parh, char *name ); |
| static void link_in( nh_t nh ); |
| static void link_out( nh_t nh ); |
| static void link_headiter( bool_t ( * cbfp )( void *contextp, nh_t hardh ), |
| void *contextp ); |
| static void link_iter_init( link_iter_context_t *link_iter_contextp, |
| nh_t hardheadh ); |
| static nh_t link_iter_next( link_iter_context_t *link_iter_contextp ); |
| void link_iter_unlink( link_iter_context_t *link_iter_contextp, nh_t nh ); |
| static bool_t hash_init( size64_t vmsz, |
| size64_t dircnt, |
| size64_t nondircnt, |
| char *perspath ); |
| static bool_t hash_sync( char *perspath ); |
| static inline size_t hash_val(xfs_ino_t ino, size_t hashmask); |
| static void hash_in( nh_t nh ); |
| static void hash_out( nh_t nh ); |
| static nh_t hash_find( xfs_ino_t ino, gen_t gen ); |
| static void hash_iter( bool_t ( * cbfp )( void *contextp, nh_t hashh ), |
| void *contextp ); |
| static void setdirattr( dah_t dah, char *path ); |
| static bool_t tsi_walkpath( char *arg, nh_t rooth, nh_t cwdh, |
| dlog_pcbp_t pcb, void *pctxp, |
| nh_t *namedhp, nh_t *parhp, nh_t *cldhp, |
| xfs_ino_t *inop, bool_t *isdirprp, bool_t *isselpr ); |
| static bool_t Node2path( nh_t nh, char *path, char *errmsg ); |
| static bool_t tree_setattr_recurse( nh_t parh, char *path ); |
| static void tsi_cmd_pwd_recurse( void *ctxp, |
| dlog_pcbp_t pcb, |
| void *pctxp, |
| nh_t nh ); |
| static int mkdir_r(char *path); |
| #ifdef TREE_CHK |
| static bool_t Node_chk( nh_t nh, nh_t *nexthashhp, nh_t *nextlnkhp ); |
| static bool_t tree_chk2( void ); |
| #endif /* TREE_CHK */ |
| |
| /* definition of locally defined global variables ****************************/ |
| |
| |
| /* definition of locally defined static variables *****************************/ |
| |
| static treepers_t *persp = 0; |
| static tran_t *tranp = 0; |
| static char *persname = PERS_NAME; |
| static char *orphname = ORPH_NAME; |
| static xfs_ino_t orphino = ORPH_INO; |
| |
| |
| /* 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 dstdirisxfspr, |
| uint32_t dumpformat, |
| bool_t truncategenpr ) |
| { |
| off64_t nodeoff; |
| char *perspath; |
| bool_t ok; |
| int rval; |
| |
| /* sanity checks |
| */ |
| assert( ! ( PERSSZ % pgsz )); |
| assert( sizeof( persp ) <= PERSSZ ); |
| assert( sizeof( node_t ) <= NODESZ ); |
| assert( ! persp ); |
| assert( ! tranp ); |
| |
| /* allocate transient state |
| */ |
| tranp = ( tran_t * )calloc( 1, sizeof( tran_t )); |
| assert( tranp ); |
| |
| tranp->t_toconlypr = toconlypr; |
| tranp->t_hkdir = hkdir; |
| tranp->t_dstdir = dstdir; |
| tranp->t_dstdirisxfspr = dstdirisxfspr; |
| |
| /* allocate a char string buffer to hold the abs. pathname |
| * of the orphanage directory file. load it with the pathname. |
| */ |
| tranp->t_orphdir = open_pathalloc( tranp->t_dstdir, |
| orphname, |
| 0 ); |
| |
| /* determine if housekeeping dir is within the destination. |
| * generate a relative path containing the difference, |
| * else null. will not restore into there. |
| */ |
| if ( strcmp( dstdir, "." )) { |
| tranp->t_hksubtree = path_diff( hkdir, dstdir ); |
| } else { |
| tranp->t_hksubtree = 0; |
| } |
| |
| /* create an orphanage, if it already exists, complain. |
| * not needed if just a table-of-contents restore. |
| */ |
| if ( ! tranp->t_toconlypr ) { |
| rval = mkdir( tranp->t_orphdir, S_IRWXU ); |
| if ( rval ) { |
| if ( errno == EEXIST ) { |
| mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_TREE, _( |
| "%s already exists: " |
| "rm -rf prior to initating restore\n"), |
| tranp->t_orphdir ); |
| } else { |
| mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_TREE, _( |
| "unable to create %s: %s\n"), |
| tranp->t_orphdir, |
| strerror( errno )); |
| } |
| return BOOL_FALSE; |
| } |
| } |
| |
| /* build a full pathname to pers. state file |
| */ |
| perspath = open_pathalloc( tranp->t_hkdir, persname, 0 ); |
| |
| /* create the persistent state file |
| */ |
| ( void )unlink( perspath ); |
| tranp->t_persfd = open( perspath, |
| O_RDWR | O_CREAT, |
| S_IRUSR | S_IWUSR ); |
| if ( tranp->t_persfd < 0 ) { |
| mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_TREE, _( |
| "could not open %s: %s\n"), |
| perspath, |
| strerror( errno )); |
| return BOOL_FALSE; |
| } |
| |
| /* mmap the persistent state |
| */ |
| assert( ! ( PERSSZ % pgsz )); |
| persp = ( treepers_t * ) mmap_autogrow( |
| PERSSZ, |
| tranp->t_persfd, |
| ( off64_t )0 ); |
| if ( persp == ( treepers_t * )-1 ) { |
| mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_TREE, _( |
| "unable to map %s: %s\n"), |
| perspath, |
| strerror( errno )); |
| return BOOL_FALSE; |
| } |
| |
| /* create the hash abstraction. it will map more of the |
| * persistent state file. |
| */ |
| ok = hash_init( vmsz / HASHSZ_PERVM, dircnt, nondircnt, perspath ); |
| if ( ! ok ) { |
| return BOOL_FALSE; |
| } |
| |
| /* initialize the node abstraction. let it's use of backing store |
| * begin immediately after the hash abstraction. give it the remainder |
| * of vm. |
| */ |
| assert( persp->p_hashsz <= ( size64_t )( OFF64MAX - ( off64_t )PERSSZ)); |
| nodeoff = ( off64_t )PERSSZ + ( off64_t )persp->p_hashsz; |
| assert( vmsz > ( size64_t )nodeoff ); |
| ok = node_init( tranp->t_persfd, |
| nodeoff, |
| NODESZ, |
| ( ix_t )offsetofmember( node_t, n_nodehkbyte ), |
| sizeof( size64_t ), /* node alignment */ |
| vmsz - ( size64_t )nodeoff, |
| dircnt + nondircnt ); |
| if ( ! ok ) { |
| return BOOL_FALSE; |
| } |
| |
| /* extract the root ino and allocate a node for |
| * the root and for the orphanage. place both nodes |
| * in the hash list. make the orphanage a child of |
| * root, and indicate he is real. |
| * |
| * Note that we assume that the root inode always |
| * has a generation count of zero - which is true. |
| */ |
| persp->p_rootino = rootino; |
| persp->p_rooth = Node_alloc( rootino, |
| 0, /* gen cnt */ |
| NRH_NULL, |
| DAH_NULL, |
| NF_ISDIR | NF_REAL ); |
| if (persp->p_rooth == NH_NULL) |
| return BOOL_FALSE; |
| link_in( persp->p_rooth ); |
| persp->p_orphh = Node_alloc( orphino, |
| 0, /* gen cnt */ |
| namreg_add( orphname, strlen( orphname )), |
| DAH_NULL, |
| NF_ISDIR | NF_REAL ); |
| if (persp->p_orphh == NH_NULL) |
| return BOOL_FALSE; |
| link_in( persp->p_orphh ); |
| adopt( persp->p_rooth, persp->p_orphh, NRH_NULL ); |
| |
| /* record if we should attempt to restore original owner/group |
| */ |
| persp->p_ownerpr = ownerpr; |
| |
| /* record if this is a full dump. can skip link processing if so |
| */ |
| persp->p_fullpr = fullpr; |
| |
| /* record if DMI event settings should be restored |
| */ |
| persp->p_restoredmpr = restoredmpr; |
| |
| /* record if truncated generation numbers are required |
| */ |
| if ( dumpformat < GLOBAL_HDR_VERSION_3 ) { |
| persp->p_truncategenpr = BOOL_TRUE; |
| mlog( MLOG_NORMAL | MLOG_DEBUG | MLOG_TREE, _( |
| "dump format version %u used truncated inode generation numbers\n"), |
| dumpformat ); |
| } else if ( truncategenpr ) { |
| persp->p_truncategenpr = BOOL_TRUE; |
| mlog( MLOG_NORMAL | MLOG_DEBUG | MLOG_TREE, _( |
| "forcing use of truncated inode generation numbers\n")); |
| } else { |
| persp->p_truncategenpr = BOOL_FALSE; |
| } |
| |
| return BOOL_TRUE; |
| } |
| |
| bool_t |
| tree_sync( char *hkdir, |
| char *dstdir, |
| bool_t toconlypr, |
| bool_t fullpr, |
| bool_t dstdirisxfspr ) |
| { |
| off64_t nodeoff; |
| char *perspath; |
| bool_t ok; |
| int rval; |
| |
| if ( persp ) { |
| return BOOL_TRUE; |
| } |
| |
| /* sanity checks |
| */ |
| assert( ! ( PERSSZ % pgsz )); |
| assert( sizeof( persp ) <= PERSSZ ); |
| assert( sizeof( node_t ) <= NODESZ ); |
| assert( ! persp ); |
| assert( ! tranp ); |
| |
| /* allocate transient state |
| */ |
| tranp = ( tran_t * )calloc( 1, sizeof( tran_t )); |
| assert( tranp ); |
| |
| tranp->t_toconlypr = toconlypr; |
| tranp->t_hkdir = hkdir; |
| tranp->t_dstdir = dstdir; |
| tranp->t_dstdirisxfspr = dstdirisxfspr; |
| |
| /* allocate a char string buffer to hold the abs. pathname |
| * of the orphanage directory file. load it with the pathname. |
| */ |
| tranp->t_orphdir = open_pathalloc( tranp->t_dstdir, |
| orphname, |
| 0 ); |
| |
| /* determine if housekeeping dir is within the destination. |
| * generate a relative path containing the difference, |
| * else null. will not restore into there. |
| */ |
| if ( strcmp( dstdir, "." )) { |
| tranp->t_hksubtree = path_diff( hkdir, dstdir ); |
| } else { |
| tranp->t_hksubtree = 0; |
| } |
| |
| /* re-create the orphanage (in case someone rmdir'ed it) |
| */ |
| rval = mkdir( tranp->t_orphdir, S_IRWXU ); |
| if ( rval && errno != EEXIST ) { |
| mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_TREE, _( |
| "unable to recreate %s: %s\n"), |
| tranp->t_orphdir, |
| strerror( errno )); |
| return BOOL_FALSE; |
| } |
| |
| /* build a full pathname to pers. state file |
| */ |
| perspath = open_pathalloc( tranp->t_hkdir, persname, 0 ); |
| |
| /* re-open the persistent state file |
| */ |
| tranp->t_persfd = open( perspath, O_RDWR ); |
| if ( tranp->t_persfd < 0 ) { |
| mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_TREE, _( |
| "could not open %s: %s\n"), |
| perspath, |
| strerror( errno )); |
| return BOOL_FALSE; |
| } |
| |
| /* mmap the persistent state |
| */ |
| assert( ! ( PERSSZ % pgsz )); |
| persp = ( treepers_t * ) mmap_autogrow( |
| PERSSZ, |
| tranp->t_persfd, |
| ( off64_t )0 ); |
| if ( persp == ( treepers_t * )-1 ) { |
| mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_TREE, _( |
| "unable to map %s: %s\n"), |
| perspath, |
| strerror( errno )); |
| return BOOL_FALSE; |
| } |
| |
| /* update the fullpr field of the persistent state to match |
| * the input of our caller. |
| */ |
| persp->p_fullpr = fullpr; |
| |
| /* regardless of the format of this dump, if the previously applied |
| * dump used truncated generation numbers, then we need to as well. |
| */ |
| if ( persp->p_truncategenpr ) { |
| mlog( MLOG_NORMAL | MLOG_DEBUG | MLOG_TREE, _( |
| "using truncated inode generation numbers for " |
| "compatibility with previously applied restore\n") ); |
| } |
| |
| /* rsynchronize with the hash abstraction. it will map more of the |
| * persistent state file. |
| */ |
| ok = hash_sync( perspath ); |
| if ( ! ok ) { |
| return BOOL_FALSE; |
| } |
| |
| /* synchronize with the node abstraction. |
| */ |
| assert( persp->p_hashsz <= ( size64_t )( OFF64MAX - ( off64_t )PERSSZ)); |
| nodeoff = ( off64_t )PERSSZ + ( off64_t )persp->p_hashsz; |
| ok = node_sync( tranp->t_persfd, nodeoff ); |
| if ( ! ok ) { |
| return BOOL_FALSE; |
| } |
| |
| /* extract the root ino and allocate a node for |
| * the root and for the orphanage. place both nodes |
| * in the hash list. make the orphanage a child of |
| * root, and indicate he is real. |
| */ |
| return BOOL_TRUE; |
| } |
| |
| bool_t |
| tree_check_dump_format( uint32_t dumpformat ) |
| { |
| if ( dumpformat < GLOBAL_HDR_VERSION_3 && !persp->p_truncategenpr ) { |
| mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_TREE, _( |
| "encountered dump format %d after a " |
| "restore of format %d or newer\n"), |
| dumpformat, GLOBAL_HDR_VERSION_3 ); |
| mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_TREE, _( |
| "to restore this series of dumps, use the -%c " |
| "option on the first restore\n"), |
| GETOPT_FMT2COMPAT ); |
| return BOOL_FALSE; |
| } |
| |
| return BOOL_TRUE; |
| } |
| |
| /* recursively descend the tree clearing REFED and DIRDUMPED and NEWORPH |
| * flags. force the orphanage to be refed and dumped, so we won't try |
| * to orphan it, and so things added to it won't look like they are |
| * referenced during ref adj. also null dirattr handles, since they are |
| * not retained across dump session restores. |
| */ |
| static void tree_marknoref_recurse( nh_t parh ); |
| |
| void |
| tree_marknoref( void ) |
| { |
| tree_marknoref_recurse( persp->p_rooth ); |
| |
| { |
| node_t *orphp; |
| orphp = Node_map( persp->p_orphh ); |
| orphp->n_flags |= ( NF_REFED | NF_DUMPEDDIR ); |
| assert( orphp->n_dah == DAH_NULL ); |
| Node_unmap( persp->p_orphh, &orphp ); |
| } |
| } |
| |
| static void |
| tree_marknoref_recurse( nh_t parh ) |
| { |
| nh_t cldh; |
| |
| { |
| node_t *parp; |
| parp = Node_map( parh ); |
| parp->n_flags &= ~( NF_REFED | NF_DUMPEDDIR | NF_NEWORPH ); |
| |
| parp->n_dah = DAH_NULL; |
| cldh = parp->n_cldh; |
| Node_unmap( parh, &parp ); |
| } |
| while ( cldh != NH_NULL ) { |
| node_t *cldp; |
| nh_t nextcldh; |
| tree_marknoref_recurse( cldh ); /* RECURSION */ |
| cldp = Node_map( cldh ); |
| nextcldh = cldp->n_sibh; |
| Node_unmap( cldh, &cldp ); |
| cldh = nextcldh; |
| } |
| } |
| |
| void |
| tree_markallsubtree( bool_t sensepr ) |
| { |
| if ( ! sensepr ) { |
| persp->p_ignoreorphpr = BOOL_TRUE; |
| } |
| selsubtree( persp->p_rooth, sensepr ); |
| } |
| |
| nh_t |
| tree_begindir( filehdr_t *fhdrp, dah_t *dahp ) |
| { |
| nh_t hardh; |
| xfs_ino_t ino = fhdrp->fh_stat.bs_ino; |
| gen_t gen = fhdrp->fh_stat.bs_gen; |
| dah_t dah; |
| |
| if ( persp->p_truncategenpr ) { |
| gen = BIGGEN2GEN( gen ); |
| } |
| |
| /* sanity check - orphino is supposed to be an unused ino! |
| */ |
| assert( ino != orphino ); |
| |
| /* lookup head of hardlink list |
| */ |
| hardh = link_hardh( ino, gen ); |
| 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, |
| fhdrp->fh_stat.bs_gen ); |
| if ( ! tranp->t_toconlypr ) { |
| assert( hardp->n_dah == DAH_NULL ); |
| hardp->n_dah = dirattr_add( fhdrp ); |
| } |
| } else if ( ! tranp->t_toconlypr && hardp->n_dah == DAH_NULL ) { |
| /* case 2: node is a dir, but had thrown away dirattr |
| */ |
| mlog( MLOG_TRACE | MLOG_TREE, |
| "directory %llu %u (%u): " |
| "updating\n", |
| ino, |
| gen, |
| fhdrp->fh_stat.bs_gen ); |
| hardp->n_dah = dirattr_add( fhdrp ); |
| } else { |
| /* case 3: already has dirattr; must be restart |
| */ |
| mlog( MLOG_TRACE | MLOG_TREE, |
| "directory %llu %u (%u): " |
| "retaining\n", |
| ino, |
| gen, |
| fhdrp->fh_stat.bs_gen ); |
| } |
| hardp->n_flags |= NF_ISDIR; |
| hardp->n_flags |= NF_DUMPEDDIR; |
| *dahp = hardp->n_dah; |
| Node_unmap( hardh, &hardp ); |
| } else { |
| /* case 4: first time seen |
| */ |
| mlog( MLOG_TRACE | MLOG_TREE, |
| "directory %llu %u (%u): " |
| "new\n", |
| ino, |
| gen, |
| fhdrp->fh_stat.bs_gen ); |
| if ( ! tranp->t_toconlypr ) { |
| dah = dirattr_add( fhdrp ); |
| } else { |
| dah = DAH_NULL; |
| } |
| hardh = Node_alloc( ino, |
| gen, |
| NRH_NULL, |
| dah, |
| NF_ISDIR | NF_NEWORPH ); |
| if (hardh == NH_NULL) |
| return NH_NULL; |
| link_in( hardh ); |
| adopt( persp->p_orphh, hardh, NRH_NULL ); |
| *dahp = dah; |
| } |
| |
| return hardh; |
| } |
| |
| rv_t |
| tree_addent( nh_t parh, xfs_ino_t ino, gen_t gen, char *name, size_t namelen ) |
| { |
| nh_t hardh; |
| |
| if ( persp->p_truncategenpr ) { |
| gen = BIGGEN2GEN( gen ); |
| } |
| |
| /* sanity check - orphino is supposed to be an unused ino! |
| */ |
| assert( ino != orphino ); |
| |
| /* don't allow entries named "orphanage" under root to be added |
| */ |
| if ( parh == persp->p_rooth && !strcmp( name, orphname )) { |
| mlog( MLOG_DEBUG | MLOG_TREE, |
| "dirent %s %llu %u: " |
| "skipping (reserved)\n", |
| name, |
| ino, |
| gen ); |
| return RV_OK; |
| } |
| |
| /* if the parent is null, just ignore |
| */ |
| if ( parh == NH_NULL ) { |
| mlog( MLOG_DEBUG | MLOG_TREE, |
| "dirent %s %llu %u: " |
| "skipping (null parent)\n", |
| name, |
| ino, |
| gen ); |
| return RV_OK; |
| } |
| |
| /* see if one or more links to this ino already exist. |
| */ |
| hardh = link_hardh( ino, gen ); |
| |
| if ( hardh != NH_NULL ) { |
| node_t *hardp; |
| hardp = Node_map( hardh ); |
| if ( hardp->n_flags & NF_ISDIR ) { |
| nh_t renameh; |
| node_t *renamep; |
| /* REFERENCED */ |
| int namebuflen; |
| |
| hardp->n_flags |= NF_REFED; |
| if ( hardp->n_parh == persp->p_orphh ) { |
| /* dir now seen as entry |
| * if in orph but REAL, must be pending rename |
| */ |
| if ( ( hardp->n_flags & NF_REAL ) |
| && |
| hardp->n_lnkh == NH_NULL ) { |
| hardp->n_lnkh = Node_alloc( ino, |
| gen, |
| NRH_NULL, |
| DAH_NULL, |
| 0 ); |
| if (hardp->n_lnkh == NH_NULL) |
| return RV_ERROR; |
| } |
| if ( hardp->n_lnkh != NH_NULL ) { |
| assert( hardp->n_flags & NF_REAL ); |
| renameh = hardp->n_lnkh; |
| renamep = Node_map( renameh ); |
| if ( renamep->n_parh == NH_NULL ) { |
| mlog( MLOG_DEBUG | MLOG_TREE, |
| "dirent %s %llu %u: " |
| "adopting (dir par)\n", |
| name, |
| ino, |
| gen ); |
| renamep->n_parh = parh; |
| } |
| if ( renamep->n_parh != parh ) { |
| mlog( MLOG_DEBUG | MLOG_TREE, |
| "dirent %s %llu %u: " |
| "re-adopting (dir par)\n", |
| name, |
| ino, |
| gen ); |
| renamep->n_parh = parh; |
| } |
| if ( renamep->n_nrh != NRH_NULL ) { |
| namebuflen |
| = |
| namreg_get( renamep->n_nrh, |
| tranp->t_namebuf, |
| sizeof( tranp->t_namebuf )); |
| assert( namebuflen > 0 ); |
| if ( strcmp( name, |
| tranp->t_namebuf )) { |
| mlog( MLOG_DEBUG |
| | |
| MLOG_TREE, |
| "dirent %s " |
| "%llu %u: " |
| "re-adopting " |
| "(dir name): " |
| "was %s\n", |
| name, |
| ino, |
| gen, |
| tranp->t_namebuf ); |
| namreg_del( |
| renamep->n_nrh ); |
| renamep->n_nrh = |
| NRH_NULL; |
| } |
| } |
| if ( renamep->n_nrh == NRH_NULL ) { |
| renamep->n_nrh |
| = |
| namreg_add( name, namelen ); |
| renamep->n_parh = parh; |
| } |
| Node_unmap( renameh, &renamep ); |
| } else { |
| nrh_t nrh; |
| |
| hardp->n_flags &= ~NF_NEWORPH; |
| assert( hardp->n_nrh == NRH_NULL ); |
| assert( hardp->n_parh != NH_NULL ); |
| nrh = disown( hardh ); |
| assert( nrh == NRH_NULL ); |
| nrh = namreg_add( name, namelen ); |
| adopt( parh, hardh, nrh ); |
| mlog( MLOG_DEBUG | MLOG_TREE, |
| "dirent %s %llu %u: " |
| "updating (dir)\n", |
| name, |
| ino, |
| gen ); |
| } |
| } else { |
| assert( hardp->n_nrh != NRH_NULL ); |
| namebuflen |
| = |
| namreg_get( hardp->n_nrh, |
| tranp->t_namebuf, |
| sizeof( tranp->t_namebuf )); |
| assert( namebuflen > 0 ); |
| if ( hardp->n_parh == parh |
| && |
| ! strcmp( tranp->t_namebuf, name )) { |
| /* dir seen as entry again |
| */ |
| if ( hardp->n_lnkh != NH_NULL ) { |
| /* rescind rename |
| */ |
| mlog( MLOG_DEBUG | MLOG_TREE, |
| "dirent %s %llu %u: " |
| "rescind rename (dir)\n", |
| name, |
| ino, |
| gen ); |
| Node_free( &hardp->n_lnkh ); |
| } else { |
| mlog( MLOG_DEBUG | MLOG_TREE, |
| "dirent %s %llu %u: " |
| "retaining (dir)\n", |
| name, |
| ino, |
| gen ); |
| } |
| } else { |
| /* dir rename |
| */ |
| nh_t renameh; |
| node_t *renamep; |
| if ( hardp->n_lnkh == NH_NULL ) { |
| renameh = Node_alloc( ino, |
| gen, |
| NRH_NULL, |
| DAH_NULL, |
| 0 ); |
| if (renameh == NH_NULL) |
| return RV_ERROR; |
| hardp->n_lnkh = renameh; |
| } else { |
| mlog( MLOG_DEBUG | MLOG_TREE, |
| "dirent %s %llu %u: " |
| "re-renaming\n", |
| name, |
| ino, |
| gen ); |
| renameh = hardp->n_lnkh; |
| renamep = Node_map( renameh ); |
| renamep->n_parh = NH_NULL; |
| if ( renamep->n_nrh |
| != |
| NRH_NULL ) { |
| namreg_del( renamep->n_nrh ); |
| } |
| renamep->n_nrh = NRH_NULL; |
| Node_unmap( renameh, &renamep ); |
| } |
| renamep = Node_map( renameh ); |
| assert( hardp->n_parh != NH_NULL ); |
| if ( hardp->n_parh != parh ) { |
| /* different parent |
| */ |
| renamep->n_parh = parh; |
| mlog( MLOG_DEBUG | MLOG_TREE, |
| "dirent %s %llu %u: " |
| "renaming (parent)\n", |
| name, |
| ino, |
| gen ); |
| } |
| if ( strcmp( tranp->t_namebuf, name )) { |
| /* different name |
| */ |
| renamep->n_nrh = |
| namreg_add( name, namelen ); |
| mlog( MLOG_DEBUG | MLOG_TREE, |
| "dirent %s %llu %u: " |
| "renaming (name)\n", |
| name, |
| ino, |
| gen ); |
| } |
| Node_unmap( renameh, &renamep ); |
| } |
| } |
| } else { |
| nh_t matchh; |
| matchh = link_matchh( hardh, parh, name ); |
| if ( matchh != NH_NULL ) { |
| /* entry already exists |
| */ |
| node_t *matchp; |
| matchp = Node_map( matchh ); |
| matchp->n_flags |= NF_REFED; |
| Node_unmap( matchh, &matchp ); |
| mlog( MLOG_DEBUG | MLOG_TREE, |
| "dirent %s %llu %u: " |
| "retaining (nondir)\n", |
| name, |
| ino, |
| gen ); |
| } else { |
| /* case 5: new hard link |
| */ |
| nrh_t nrh; |
| nh_t linkh; |
| nrh = namreg_add( name, namelen ); |
| linkh = Node_alloc( ino, |
| gen, |
| NRH_NULL, |
| DAH_NULL, |
| NF_REFED ); |
| if (linkh == NH_NULL) |
| return RV_ERROR; |
| link_in( linkh ); |
| adopt( parh, linkh, nrh ); |
| mlog( MLOG_DEBUG | MLOG_TREE, |
| "dirent %s %llu %u: " |
| "adding (link)\n", |
| name, |
| ino, |
| gen ); |
| } |
| } |
| Node_unmap( hardh, &hardp ); |
| } else { |
| /* case 6: new entry |
| */ |
| nrh_t nrh; |
| nrh = namreg_add( name, namelen ); |
| hardh = Node_alloc( ino, |
| gen, |
| NRH_NULL, |
| DAH_NULL, |
| NF_REFED ); |
| if (hardh == NH_NULL) |
| return RV_ERROR; |
| link_in( hardh ); |
| adopt( parh, hardh, nrh ); |
| mlog( MLOG_DEBUG | MLOG_TREE, |
| "dirent %s %llu %u: " |
| "adding (new)\n", |
| name, |
| ino, |
| gen ); |
| } |
| 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; |
| int rval; |
| bool_t ok; |
| |
| cldp = Node_map( cldh ); |
| ino = cldp->n_ino; |
| gen = cldp->n_gen; |
| inorphanagepr = cldp->n_parh == persp->p_orphh; |
| isdirpr = ( cldp->n_flags & NF_ISDIR ); |
| isrealpr = ( cldp->n_flags & NF_REAL ); |
| isrefpr = ( cldp->n_flags & NF_REFED ); |
| 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 ) { |
| int rval; |
| |
| if ( ! Node2path( cldh, path, _("makedir") )) { |
| cldh = nextcldh; |
| continue; |
| } |
| if ( tranp->t_toconlypr ) { |
| rval = 0; |
| } else { |
| mlog( MLOG_TRACE | MLOG_TREE, |
| "mkdir %s\n", |
| path ); |
| rval = mkdir( path, S_IRWXU ); |
| } |
| if ( rval && errno != EEXIST ) { |
| mlog( MLOG_NORMAL | MLOG_WARNING | MLOG_TREE, _( |
| "mkdir %s failed: %s\n"), |
| path, |
| strerror( errno )); |
| |
| } else { |
| cldp = Node_map( cldh ); |
| cldp->n_flags |= NF_REAL; |
| Node_unmap( cldh, &cldp ); |
| isrealpr = BOOL_TRUE; |
| } |
| } |
| |
| /* if a real selected directory, recurse |
| */ |
| if ( isdirpr && isrealpr && isselpr ) { |
| bool_t ok; |
| ok = mkdirs_recurse( cldh, grandcldh, path ); |
| /* RECURSION */ |
| if ( ! ok ) { |
| return BOOL_FALSE; |
| } |
| } |
| |
| /* next! |
| */ |
| cldh = nextcldh; |
| } |
| |
| return BOOL_TRUE; |
| } |
| |
| static bool_t |
| rename_dirs( nh_t cldh, |
| char *path1, |
| char *path2 ) |
| { |
| while ( cldh != NH_NULL ) { |
| node_t *cldp; |
| /* REFERENCED */ |
| nh_t parh; |
| bool_t isdirpr; |
| nh_t renameh; |
| bool_t isrenamepr; |
| nh_t nextcldh; |
| nh_t newparh; |
| nh_t newnrh; |
| |
| cldp = Node_map( cldh ); |
| parh = cldp->n_parh; |
| isdirpr = cldp->n_flags & NF_ISDIR; |
| renameh = cldp->n_lnkh; |
| isrenamepr = isdirpr && renameh != NH_NULL; |
| nextcldh = cldp->n_sibh; |
| Node_unmap( cldh, &cldp ); |
| assert( parh == persp->p_orphh ); |
| |
| if ( isrenamepr ) { |
| node_t *renamep; |
| int rval; |
| /* REFERENCED */ |
| nrh_t dummynrh; |
| bool_t ok; |
| |
| renamep = Node_map( renameh ); |
| newparh = renamep->n_parh; |
| newnrh = renamep->n_nrh; |
| Node_unmap( renameh, &renamep ); |
| ok = Node2path( cldh, path1, _("rename dir") ); |
| if ( ! ok ) { |
| cldh = nextcldh; |
| continue; |
| } |
| dummynrh = disown( cldh ); |
| assert( dummynrh == NRH_NULL ); |
| adopt( newparh, cldh, newnrh ); |
| ok = Node2path( cldh, path2, _("rename dir") ); |
| if ( ! ok ) { |
| dummynrh = disown( cldh ); |
| assert( dummynrh == newnrh ); |
| adopt( persp->p_orphh, cldh, NRH_NULL ); |
| cldp = Node_map( cldh ); |
| cldp->n_nrh = NRH_NULL; |
| Node_unmap( cldh, &cldp ); |
| cldh = nextcldh; |
| continue; |
| } |
| mlog( MLOG_TRACE | MLOG_TREE, |
| "rename dir %s to %s\n", |
| path1, |
| path2 ); |
| rval = rename( path1, path2 ); |
| if ( rval ) { |
| mlog( MLOG_NORMAL | MLOG_WARNING, _( |
| "unable to rename dir %s " |
| "to dir %s: %s\n"), |
| path1, |
| path2, |
| strerror( errno )); |
| dummynrh = disown( cldh ); |
| assert( dummynrh == newnrh ); |
| adopt( persp->p_orphh, cldh, NRH_NULL ); |
| cldh = nextcldh; |
| continue; |
| } |
| cldp = Node_map( cldh ); |
| cldp->n_flags &= ~NF_NEWORPH; |
| cldp->n_lnkh = NH_NULL; |
| Node_unmap( cldh, &cldp ); |
| Node_free( &renameh ); |
| } |
| |
| /* next! |
| */ |
| cldh = nextcldh; |
| } |
| |
| return BOOL_TRUE; |
| } |
| |
| /* will call funcp for all links to be created. will abort if funcp |
| * ever returns FALSE; |
| */ |
| rv_t |
| tree_cb_links( xfs_ino_t ino, |
| gen_t gen, |
| int32_t ctime, |
| int32_t mtime, |
| bool_t ( * funcp )( void *contextp, |
| bool_t linkpr, |
| char *path1, |
| char *path2 ), |
| void *contextp, |
| char *path1, |
| char *path2 ) |
| { |
| nh_t hardh; |
| nh_t nh; |
| char *path; |
| bool_t ok; |
| int rval; |
| |
| if ( persp->p_truncategenpr ) { |
| gen = BIGGEN2GEN( gen ); |
| } |
| |
| /* find the hardhead |
| */ |
| hardh = link_hardh( ino, gen ); |
| |
| /* loop through all hard links, attempting to restore/link |
| */ |
| path = path1; |
| for ( nh = hardh ; nh != NH_NULL ; nh = link_nexth( nh )) { |
| node_t *np; |
| u_char_t flags; |
| char *reasonstr; |
| |
| /* get the node flags |
| */ |
| np = Node_map( nh ); |
| flags = np->n_flags; |
| Node_unmap( nh, &np ); |
| |
| /* build a pathname |
| */ |
| ok = Node2path( nh, path, _("restore") ); |
| if ( ! ok ) { |
| continue; |
| } |
| |
| /* skip if not in selected subtree |
| */ |
| if ( ! ( flags & NF_SUBTREE )) { |
| mlog( (MLOG_NITTY + 1) | MLOG_TREE, |
| "skipping %s (ino %llu gen %u): %s\n", |
| path, |
| ino, |
| gen, |
| "not in selected subtree" ); |
| continue; |
| } |
| |
| /* don't restore into the housekeeping directory |
| */ |
| if ( path_beginswith( path, tranp->t_hkdir )) { |
| mlog( MLOG_NORMAL | MLOG_WARNING | MLOG_TREE, _( |
| "cannot restore %s (ino %llu gen %u): " |
| "pathname contains %s\n"), |
| path, |
| ino, |
| gen, |
| tranp->t_hkdir ); |
| continue; |
| } |
| |
| /* check if ok to overwrite: don't check if we've already |
| * been here and decided overwrite ok. if ok, set flag |
| * so we won't check again. in fact, can't check again |
| * since restore changes the answer. |
| */ |
| if ( ! ( flags & NF_WRITTEN )) { |
| bool_t exists; |
| if ( ! content_overwrite_ok( path, |
| ctime, |
| mtime, |
| &reasonstr, |
| &exists )) { |
| mlog( MLOG_TRACE | MLOG_TREE, |
| "skipping %s (ino %llu gen %u): %s\n", |
| path, |
| ino, |
| gen, |
| reasonstr ); |
| continue; |
| } else { |
| np = Node_map( nh ); |
| np->n_flags |= NF_WRITTEN; |
| Node_unmap( nh, &np ); |
| |
| /* |
| * We're the first stream to restore this file. |
| * Unlink it first to remove extended attributes |
| * that may have been set since the dump was |
| * taken. |
| */ |
| if ( ! tranp->t_toconlypr && exists ) { |
| rval = unlink( path ); |
| if ( rval && errno != ENOENT ) { |
| mlog( MLOG_NORMAL | |
| MLOG_WARNING, _( |
| "unable to unlink " |
| "current file prior to " |
| "restore: " |
| "%s: %s: discarding\n"), |
| path, |
| strerror( errno )); |
| continue; |
| } |
| } |
| } |
| } |
| |
| /* call callback to restore file / create hard link. |
| * returns !ok if should abort. |
| */ |
| if ( path == path2 ) { |
| mlog( MLOG_TRACE | MLOG_TREE, |
| "link %s to %s (%llu %u)\n", |
| path1, |
| path2, |
| ino, |
| gen ); |
| } else { |
| mlog( MLOG_TRACE | MLOG_TREE, |
| "restoring %s (%llu %u)\n", |
| path1, |
| ino, |
| gen ); |
| } |
| ok = ( * funcp )( contextp, path == path2, path1, path2 ); |
| if ( ! ok ) { |
| return RV_NOTOK; |
| } |
| |
| /* set flag, indicating node is now real |
| */ |
| np = Node_map( nh ); |
| np->n_flags |= NF_REAL; |
| Node_unmap( nh, &np ); |
| |
| /* switch to second path buffer, for link paths |
| */ |
| path = path2; |
| } |
| |
| |
| /* if not yet peeled from tape, do so: place in orphanage if |
| * no references found (i.e., hard link list empty). |
| */ |
| if ( path == path1 ) { |
| if ( hardh != NH_NULL || persp->p_ignoreorphpr ) { |
| /* |
| * The file is referenced so the path is |
| * valid. This means that if path begins |
| * with 'orphanage' the file is one of two |
| * things: |
| * 1) It's a file that really is an |
| * orphanage file from a previous restore |
| * that has now ended up on this dump tape. |
| * We don't really want to restore this file |
| * but, it's harmless to do so, it should |
| * happen rarely, and the path name is |
| * indistinguishable from ... |
| * 2) A file whose name was never resolved |
| * from root because of file corruption. |
| * Some granparent dir (parent dir of it's |
| * parent dir) was corrupted so the code that |
| * walks the trees was thus never able to set |
| * the NF_SUBTREE flag. It then ends up here |
| * with a non-resolved name but a valid |
| * hard reference. We really need to give |
| * the admin the opportunity to get this |
| * data since one corrupted dirnode would |
| * make the whole tree of data |
| * unreachable. pv698761 |
| */ |
| if ( persp->p_ignoreorphpr || (strncmp(ORPH_NAME, path, |
| strlen(ORPH_NAME)) != 0)) { |
| mlog( MLOG_DEBUG | MLOG_TREE, |
| "discarding %llu %u\n", |
| ino, |
| gen ); |
| ok = ( * funcp )( contextp, BOOL_FALSE, 0, 0 ); |
| if ( ! ok ) { |
| return RV_NOTOK; |
| } |
| } else { |
| |
| if ( ! tranp->t_toconlypr ) { |
| char *dir; |
| char tmp[PATH_MAX]; |
| |
| strcpy(tmp, path); |
| dir = dirname(tmp); |
| mkdir_r(dir); |
| } |
| |
| mlog (MLOG_VERBOSE | MLOG_NOTE | MLOG_TREE, _( |
| "ino %llu salvaging file," |
| " placing in %s\n"), ino, path1); |
| ok = ( * funcp )( contextp, path == path2, |
| path1, path2 ); |
| if ( ! ok ) { |
| return RV_NOTOK; |
| } |
| } |
| } else { |
| mlog( MLOG_VERBOSE | MLOG_NOTE | MLOG_TREE, _( |
| "ino %llu gen %u not referenced: " |
| "placing in orphanage\n"), |
| ino, |
| gen ); |
| nh = Node_alloc( ino, |
| gen, |
| NRH_NULL, |
| DAH_NULL, |
| NF_REAL | NF_NEWORPH ); |
| if (nh == NH_NULL) { |
| mlog( MLOG_ERROR | MLOG_TREE, _( |
| "node allocation failed when placing ino %llu" |
| " in orphanage\n"), ino); |
| return RV_ERROR; /* allocation failed */ |
| } |
| link_in( nh ); |
| adopt( persp->p_orphh, nh, NRH_NULL ); |
| ok = Node2path( nh, path1, _("orphan") ); |
| assert( ok ); |
| ( void )( * funcp )( contextp, BOOL_FALSE, path1,path2); |
| } |
| } |
| return RV_OK; |
| } |
| |
| /* uses flags cleared during directory restore (NF_DUMPEDDIR and NF_REFED ) |
| * to determine what directory entries are no longer needed. this can |
| * be done because whenever a directory chenges, it and all of its current |
| * entries are dumped. so if an entry is dumped which is a dir, but that |
| * dir is not dumped, all of that directories entries are needed and so must |
| * have their REFED flag cleared. |
| * |
| * does a recursive depth-wise descent of the tree, following this rule to |
| * clear the REFED flags. |
| */ |
| |
| static void tree_adjref_recurse( nh_t cldh, |
| bool_t pardumpedpr, |
| bool_t parrefedpr ); |
| |
| bool_t |
| tree_adjref( void ) |
| { |
| tree_adjref_recurse( persp->p_rooth, BOOL_FALSE, BOOL_TRUE ); |
| return BOOL_TRUE; |
| } |
| |
| static void |
| tree_adjref_recurse( nh_t cldh, |
| bool_t pardumpedpr, |
| bool_t parrefedpr ) |
| { |
| nh_t grandcldh; |
| bool_t clddumpedpr; |
| bool_t cldrefedpr; |
| { |
| node_t *cldp; |
| cldp = Node_map( cldh ); |
| if ( ! pardumpedpr && parrefedpr ) { |
| cldp->n_flags |= NF_REFED; |
| } |
| clddumpedpr = ( int )cldp->n_flags & NF_DUMPEDDIR; |
| cldrefedpr = ( int )cldp->n_flags & NF_REFED; |
| grandcldh = cldp->n_cldh; |
| Node_unmap( cldh, &cldp ); |
| } |
| while ( grandcldh != NH_NULL ) { |
| node_t *grandcldp; |
| nh_t nextgrandcldh; |
| tree_adjref_recurse( grandcldh, clddumpedpr, cldrefedpr ); |
| /* RECURSION */ |
| grandcldp = Node_map( grandcldh ); |
| nextgrandcldh = grandcldp->n_sibh; |
| Node_unmap( grandcldh, &grandcldp ); |
| grandcldh = nextgrandcldh; |
| } |
| } |
| |
| static bool_t tree_extattr_recurse( nh_t parh, |
| nh_t cldh, |
| bool_t ( * cbfunc )( char *path, |
| dah_t dah ), |
| char *path ); |
| |
| bool_t |
| tree_extattr( bool_t ( * cbfunc )( char *path, dah_t dah ), char *path ) |
| { |
| node_t *rootp; |
| nh_t cldh; |
| bool_t ok; |
| |
| rootp = Node_map( persp->p_rooth ); |
| cldh = rootp->n_cldh; |
| Node_unmap( persp->p_rooth, &rootp ); |
| ok = tree_extattr_recurse( persp->p_rooth, cldh, cbfunc, path ); |
| |
| return ok; |
| } |
| |
| static bool_t |
| tree_extattr_recurse( nh_t parh, |
| nh_t cldh, |
| bool_t ( * cbfunc )( char *path, dah_t dah ), |
| char *path ) |
| { |
| node_t *parp; |
| dah_t dah; |
| bool_t ok; |
| |
| /* first update all children |
| */ |
| while ( cldh != NH_NULL ) { |
| node_t *cldp; |
| bool_t isdirpr; |
| bool_t isselpr; |
| bool_t isrealpr; |
| nh_t grandcldh; |
| nh_t nextcldh; |
| |
| cldp = Node_map( cldh ); |
| isdirpr = ( cldp->n_flags & NF_ISDIR ); |
| isrealpr = ( cldp->n_flags & NF_REAL ); |
| isselpr = ( cldp->n_flags & NF_SUBTREE ); |
| grandcldh = cldp->n_cldh; |
| nextcldh = cldp->n_sibh; |
| Node_unmap( cldh, &cldp ); |
| |
| /* if a real selected directory, recurse |
| */ |
| if ( isdirpr && isrealpr && isselpr ) { |
| bool_t ok; |
| ok = tree_extattr_recurse( cldh, |
| grandcldh, |
| cbfunc, |
| path ); |
| /* RECURSION */ |
| if ( ! ok ) { |
| return BOOL_FALSE; |
| } |
| } |
| |
| /* next! |
| */ |
| cldh = nextcldh; |
| } |
| |
| /* now update self |
| */ |
| parp = Node_map( parh ); |
| dah = parp->n_dah; |
| Node_unmap( parh, &parp ); |
| if ( ! Node2path( parh, path, _("set dir extattr") )) { |
| mlog (MLOG_NORMAL | MLOG_WARNING | MLOG_TREE, _( |
| "tree_extattr_recurse: Could not convert node to " |
| "path for %s\n"), path); |
| return BOOL_TRUE; |
| } |
| if ( dah != DAH_NULL ) { |
| ok = ( * cbfunc )( path, dah ); |
| } else { |
| ok = BOOL_TRUE; |
| } |
| |
| return ok; |
| } |
| |
| struct phcb { |
| char *path1; |
| char *path2; |
| bool_t ok; |
| }; |
| |
| typedef struct phcb phcb_t; |
| |
| static bool_t proc_hardlinks_cb( void *contextp, nh_t hardheadh ); |
| |
| static bool_t |
| proc_hardlinks( char *path1, char *path2 ) |
| { |
| phcb_t phcb; |
| |
| /* have callback invoked for all hardheads |
| */ |
| phcb.path1 = path1; |
| phcb.path2 = path2; |
| phcb.ok = BOOL_TRUE; |
| link_headiter( proc_hardlinks_cb, ( void * )&phcb ); |
| return phcb.ok; |
| } |
| |
| /* called for every hard head |
| * |
| * tes@sgi.com: |
| * This code processes the hardlinks, extracting out a |
| * src_list - real & !ref |
| * dest_list - !real & ref |
| * The src_list are the entries to delete and the dst_list |
| * are the new_entries to create. |
| * 1. If we have a src and a dst then we can do a rename. |
| * 2. If we have extra src nodes then we can unlink them. |
| * 3. If we have extra dst nodes then we can link them. |
| * HOWEVER, the linking in of the new nodes is actually handled |
| * in the restore_file_cb() code which on restoral creates |
| * the file and all its hardlinks. |
| * ALSO, I can not see how the src_list can have more than |
| * 1 node in it, since in noref_elim_recurse() it unlinks all |
| * extra deleted entries (real & !ref). |
| * Thus, steps 2 and 3 are handled in noref_elim_recurse(). |
| */ |
| static bool_t |
| proc_hardlinks_cb( void *contextp, nh_t hardheadh ) |
| { |
| phcb_t *phcbp = ( phcb_t * )contextp; |
| node_t *hardheadp; |
| xfs_ino_t ino; |
| gen_t gen; |
| bool_t isdirpr; |
| nh_t rnsrcheadh; |
| nh_t rndstheadh; |
| nh_t lnsrch; |
| nh_t nh; |
| link_iter_context_t link_iter_context; |
| bool_t ok; |
| int rval; |
| |
| /* skip directories |
| */ |
| hardheadp = Node_map( hardheadh ); |
| ino = hardheadp->n_ino; |
| gen = hardheadp->n_gen; |
| isdirpr = hardheadp->n_flags & NF_ISDIR; |
| Node_unmap( hardheadh, &hardheadp ); |
| if ( isdirpr ) { |
| return BOOL_TRUE; |
| } |
| |
| mlog( MLOG_DEBUG | MLOG_TREE, |
| "processing hardlinks to %llu %u\n", |
| ino, |
| gen ); |
| |
| /* first pass through hard link list: for each node, leave on |
| * list, unlink and place on rename src list, unlink and place on |
| * rename dst list, or unlink and discard. note a node available |
| * to link from, in case we need it. |
| */ |
| rnsrcheadh = NH_NULL; |
| rndstheadh = NH_NULL; |
| lnsrch = NH_NULL; |
| link_iter_init( &link_iter_context, hardheadh ); |
| while ( ( nh = link_iter_next( &link_iter_context )) != NH_NULL ) { |
| |
| node_t *np = Node_map( nh ); |
| bool_t isrealpr = np->n_flags & NF_REAL; |
| bool_t isrefpr = np->n_flags & NF_REFED; |
| bool_t isselpr = np->n_flags & NF_SUBTREE; |
| |
| /* if unrefed, unreal, free node etc. (sel doesn't matter) |
| */ |
| if ( ! isrealpr && ! isrefpr ) { |
| mlog( MLOG_NITTY | MLOG_TREE, |
| "freeing node %x: not real, not referenced\n", |
| nh ); |
| link_iter_unlink( &link_iter_context, nh ); |
| Node_unmap( nh, &np ); |
| ( void )disown( nh ); |
| Node_free( &nh ); |
| continue; |
| } |
| |
| /* not real, refed, but not selected, can't help |
| */ |
| if ( ! isrealpr && isrefpr && ! isselpr ) { |
| mlog( MLOG_NITTY | MLOG_TREE, |
| "skipping node %x: not selected\n", |
| nh ); |
| Node_unmap( nh, &np ); |
| continue; |
| } |
| |
| /* if unreal, refed, sel, add to dst list, |
| */ |
| if ( ! isrealpr && isrefpr && isselpr ) { |
| mlog( MLOG_NITTY | MLOG_TREE, |
| "making node %x dst: " |
| "not real, refed, sel\n", |
| nh ); |
| link_iter_unlink( &link_iter_context, nh ); |
| np->n_lnkh = rndstheadh; |
| rndstheadh = nh; |
| Node_unmap( nh, &np ); |
| continue; |
| } |
| |
| /* if real, unrefed, sel, add to src list |
| */ |
| if ( isrealpr && ! isrefpr && isselpr ) { |
| mlog( MLOG_NITTY | MLOG_TREE, |
| "making node %x src: real, not refed, sel\n", |
| nh ); |
| link_iter_unlink( &link_iter_context, nh ); |
| np->n_lnkh = rnsrcheadh; |
| rnsrcheadh = nh; |
| Node_unmap( nh, &np ); |
| continue; |
| } |
| |
| /* would like to get rid of but not selected, or |
| * real and referenced, leave alone (sel doesn't matter). |
| * consider as a lnk src, since real and not going away. |
| */ |
| if ( isrealpr && ( isrefpr || !isselpr ) ) { |
| mlog( MLOG_NITTY | MLOG_TREE, |
| "skipping node %x: %s\n", |
| nh, |
| isselpr ? "real and ref" : "real and not sel" ); |
| Node_unmap( nh, &np ); |
| if ( lnsrch == NH_NULL ) { |
| mlog( MLOG_NITTY | MLOG_TREE, |
| "node %x will be link src\n", |
| nh ); |
| lnsrch = nh; |
| } |
| continue; |
| } |
| assert( 0 ); |
| } |
| |
| /* now pass through dst list, doing renames if src list not empty, |
| * otherwise links if a lnk src available, otherwise put back in link |
| * list |
| */ |
| while ( rndstheadh != NH_NULL ) { |
| nh_t dsth; |
| node_t *dstp; |
| bool_t successpr; |
| |
| dsth = rndstheadh; |
| dstp = Node_map( dsth ); |
| rndstheadh = dstp->n_lnkh; |
| dstp->n_lnkh = NH_NULL; |
| Node_unmap( dsth, &dstp ); |
| |
| /* build pathname to dst |
| */ |
| ok = Node2path( dsth, phcbp->path2, _("rename to") ); |
| if ( ! ok ) { |
| link_in( dsth ); |
| continue; |
| } |
| |
| successpr = BOOL_FALSE; |
| while ( ! successpr && rnsrcheadh != NH_NULL ) { |
| nh_t srch; |
| nrh_t nrh; |
| node_t *srcp; |
| |
| srch = rnsrcheadh; |
| srcp = Node_map( srch ); |
| rnsrcheadh = srcp->n_lnkh; |
| srcp->n_lnkh = NH_NULL; |
| Node_unmap( srch, &srcp ); |
| |
| /* build a path to src |
| */ |
| ok = Node2path( srch, phcbp->path1, _("rename from") ); |
| if ( ! ok ) { |
| link_in( srch ); |
| continue; |
| } |
| |
| if ( tranp->t_toconlypr ) { |
| rval = 0; |
| } else { |
| mlog( MLOG_TRACE | MLOG_TREE, |
| "rename nondir %s to %s\n", |
| phcbp->path1, |
| phcbp->path2 ); |
| rval = rename( phcbp->path1, |
| phcbp->path2 ); |
| } |
| if ( rval ) { |
| mlog( MLOG_NORMAL | MLOG_WARNING | MLOG_TREE, _( |
| "unable to rename nondir " |
| "%s to %s: %s\n"), |
| phcbp->path1, |
| phcbp->path2, |
| strerror( errno )); |
| link_in( srch ); |
| continue; |
| } |
| |
| nrh = disown( srch ); |
| if ( nrh != NRH_NULL ) { |
| namreg_del( nrh ); |
| } |
| Node_free( &srch ); |
| |
| successpr = BOOL_TRUE; |
| } |
| |
| /* tes@sgi.com: note: loop of one iteration only |
| */ |
| while ( ! successpr && lnsrch != NH_NULL ) { |
| ok = Node2path( lnsrch, phcbp->path1, _("link") ); |
| |
| if ( ! ok ) { |
| mlog( MLOG_NORMAL | MLOG_WARNING | MLOG_TREE, _( |
| "unable to use %s " |
| "as a hard link source\n"), |
| phcbp->path1 ); |
| lnsrch = NH_NULL; |
| continue; |
| } |
| |
| if ( tranp->t_toconlypr ) { |
| rval = 0; |
| } else { |
| mlog( MLOG_TRACE | MLOG_TREE, |
| "link nondir %s to %s\n", |
| phcbp->path1, |
| phcbp->path2 ); |
| rval = link( phcbp->path1, |
| phcbp->path2 ); |
| } |
| if ( rval ) { |
| mlog( MLOG_NORMAL | MLOG_WARNING | MLOG_TREE, |
| "unable to link nondir " |
| "%s to %s: %s\n", |
| phcbp->path1, |
| phcbp->path2, |
| strerror( errno )); |
| break; |
| } |
| successpr = BOOL_TRUE; |
| } |
| |
| if ( ! successpr ) { |
| mlog( MLOG_NITTY | MLOG_TREE, |
| "no link src for node %x\n", |
| dsth ); |
| } else { |
| dstp = Node_map( dsth ); |
| dstp->n_flags |= NF_REAL; |
| Node_unmap( dsth, &dstp ); |
| } |
| |
| link_in( dsth ); |
| } |
| |
| /* finally, pass through remaining src list, unlinking/disowning. |
| * tes@sgi.com: don't believe this will happen as this step |
| * should now be done in noref_elim_recurse(). |
| */ |
| while ( rnsrcheadh != NH_NULL ) { |
| nh_t srch; |
| node_t *srcp; |
| bool_t ok; |
| |
| srch = rnsrcheadh; |
| srcp = Node_map( srch ); |
| rnsrcheadh = srcp->n_lnkh; |
| srcp->n_lnkh = NH_NULL; |
| Node_unmap( srch, &srcp ); |
| |
| ok = Node2path( srch, phcbp->path1, _("unlink") ); |
| if ( ! ok ) { |
| link_in( srch ); |
| continue; |
| } |
| |
| if ( tranp->t_toconlypr ) { |
| rval = 0; |
| } else { |
| mlog( MLOG_TRACE | MLOG_TREE, |
| "unlink nondir %s\n", |
| phcbp->path1 ); |
| rval = unlink( phcbp->path1 ); |
| } |
| if ( rval ) { |
| mlog( MLOG_NORMAL | MLOG_WARNING | MLOG_TREE, _( |
| "unable to unlink %s: %s\n"), |
| phcbp->path1, |
| strerror( errno )); |
| link_in( srch ); |
| } else { |
| nrh_t nrh; |
| nrh = disown( srch ); |
| if ( nrh != NRH_NULL ) { |
| namreg_del( nrh ); |
| } |
| Node_free( &srch ); |
| } |
| } |
| |
| return BOOL_TRUE; |
| } |
| |
| /* traverse tree depth-wise bottom-up for dirs no longer referenced. |
| * if non-empty, move children to orphanage |
| */ |
| bool_t |
| tree_setattr( char *path ) |
| { |
| bool_t ok; |
| node_t *rootp; |
| |
| ok = tree_setattr_recurse( persp->p_rooth, path ); |
| |
| if ( restore_rootdir_permissions && ok ) { |
| rootp = Node_map( persp->p_rooth ); |
| /* "." is cwd which is the destination dir */ |
| setdirattr( rootp->n_dah, "." ); |
| Node_unmap( persp->p_rooth, &rootp ); |
| } |
| |
| return ok; |
| } |
| |
| static bool_t |
| tree_setattr_recurse( nh_t parh, char *path ) |
| { |
| node_t *parp = Node_map( parh ); |
| nh_t cldh = parp->n_cldh; |
| Node_unmap( parh, &parp ); |
| while ( cldh != NH_NULL ) { |
| nh_t nextcldh; |
| |
| /* get the node attributes |
| */ |
| node_t *cldp = Node_map( cldh ); |
| bool_t isdirpr = ( cldp->n_flags & NF_ISDIR ); |
| bool_t isselpr = ( cldp->n_flags & NF_SUBTREE ); |
| bool_t isrealpr = ( cldp->n_flags & NF_REAL ); |
| dah_t dah = cldp->n_dah; |
| |
| /* get next cld |
| */ |
| nextcldh = cldp->n_sibh; |
| Node_unmap( cldh, &cldp ); |
| |
| /* if is a real selected dir, go ahead. |
| */ |
| if ( isdirpr && isselpr && isrealpr ) { |
| bool_t ok; |
| ok = tree_setattr_recurse( cldh, path ); /* RECURSION */ |
| if ( ! ok ) { |
| Node_unmap( cldh, &cldp ); |
| return BOOL_FALSE; |
| } |
| if ( dah != DAH_NULL ) { |
| bool_t ok; |
| ok = Node2path( cldh, path, _("set dirattr") ); |
| if ( ok ) { |
| setdirattr( dah, path ); |
| } |
| } |
| } |
| |
| cldh = nextcldh; |
| } |
| |
| return BOOL_TRUE; |
| } |
| |
| static void |
| setdirattr( dah_t dah, char *path ) |
| { |
| mode_t mode; |
| struct utimbuf utimbuf; |
| struct fsxattr fsxattr; |
| int rval; |
| size_t hlen; |
| void *hanp; |
| int fd = -1; |
| |
| if ( dah == DAH_NULL ) |
| return; |
| |
| if ( tranp->t_dstdirisxfspr ) { |
| if (path_to_handle(path, &hanp, &hlen)) { |
| mlog( MLOG_NORMAL | MLOG_WARNING, |
| _("path_to_handle of %s failed:%s\n"), |
| path, strerror( errno )); |
| } else { |
| fd = open_by_handle(hanp, hlen, O_RDONLY); |
| if (fd < 0) { |
| mlog( MLOG_NORMAL | MLOG_WARNING, |
| _("open_by_handle of %s failed:%s\n"), |
| path, strerror( errno )); |
| } |
| free_handle(hanp, hlen); |
| } |
| } |
| |
| if ( tranp->t_dstdirisxfspr && persp->p_restoredmpr ) { |
| fsdmidata_t fssetdm; |
| |
| fssetdm.fsd_dmevmask = dirattr_get_dmevmask( dah ); |
| fssetdm.fsd_padding = 0; /* not used */ |
| fssetdm.fsd_dmstate = ( uint16_t )dirattr_get_dmstate( dah ); |
| |
| /* restore DMAPI event settings etc. |
| */ |
| 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 )); |
| } |
| } |
| |
| utimbuf.actime = dirattr_get_atime( dah ); |
| utimbuf.modtime = dirattr_get_mtime( dah ); |
| rval = utime( path, &utimbuf ); |
| if ( rval ) { |
| mlog( MLOG_VERBOSE | MLOG_TREE, _( |
| "could not set access and modification times" |
| " of %s: %s\n"), |
| path, |
| strerror( errno )); |
| } |
| mode = dirattr_get_mode( dah ); |
| if ( persp->p_ownerpr ) { |
| rval = chown( path, |
| dirattr_get_uid( dah ), |
| dirattr_get_gid( dah )); |
| if ( rval ) { |
| mlog( MLOG_NORMAL | MLOG_TREE, _( |
| "chown (uid=%d, gid=%d) %s failed: %s\n"), |
| dirattr_get_uid( dah ), |
| dirattr_get_gid( dah ), |
| path, |
| strerror( errno )); |
| } |
| } |
| rval = chmod( path, mode ); |
| if ( rval ) { |
| mlog( MLOG_NORMAL | MLOG_TREE, _( |
| "chmod %s failed: %s\n"), |
| path, |
| strerror( errno )); |
| } |
| |
| /* set the extended inode flags |
| */ |
| if ( !tranp->t_dstdirisxfspr ) |
| return; |
| |
| memset((void *)&fsxattr, 0, sizeof( fsxattr )); |
| fsxattr.fsx_xflags = dirattr_get_xflags( dah ); |
| fsxattr.fsx_extsize = dirattr_get_extsize( dah ); |
| fsxattr.fsx_projid = dirattr_get_projid( dah ); |
| |
| rval = ioctl( fd, |
| XFS_IOC_FSSETXATTR, |
| (void *)&fsxattr); |
| if ( rval < 0 ) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, |
| _("attempt to set " |
| "extended attributes " |
| "(xflags 0x%x, " |
| "extsize = 0x%x, " |
| "projid = 0x%x) " |
| "of %s failed: " |
| "%s\n"), |
| fsxattr.fsx_xflags, |
| fsxattr.fsx_extsize, |
| fsxattr.fsx_projid, |
| path, |
| strerror(errno)); |
| } |
| ( void )close( fd ); |
| } |
| |
| /* deletes orphanage if empty, else warns |
| */ |
| bool_t |
| tree_delorph( void ) |
| { |
| int rval; |
| |
| rval = rmdir( tranp->t_orphdir ); |
| if ( rval ) { |
| if ( errno == EEXIST ) { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_TREE, _( |
| "unable to rmdir %s: not empty\n"), |
| tranp->t_orphdir ); |
| } else { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_TREE, _( |
| "unable to rmdir %s: %s\n"), |
| tranp->t_orphdir, |
| strerror( errno )); |
| } |
| } |
| |
| return BOOL_TRUE; |
| } |
| |
| |
| /* definition of locally defined static functions ****************************/ |
| |
| |
| /* interactive subtree abstraction *******************************************/ |
| |
| static void tsi_cmd_inst( void *, dlog_pcbp_t, void * ); |
| static void tsi_cmd_pwd( void *, dlog_pcbp_t, void * ); |
| static void tsi_cmd_ls( void *, dlog_pcbp_t, void * ); |
| static void tsi_cmd_cd( void *, dlog_pcbp_t, void * ); |
| static void tsi_cmd_add( void *, dlog_pcbp_t, void * ); |
| static void tsi_cmd_delete( void *, dlog_pcbp_t, void * ); |
| static void tsi_cmd_extract( void *, dlog_pcbp_t, void * ); |
| static void tsi_cmd_quit( void *, dlog_pcbp_t, void * ); |
| static void tsi_cmd_help( void *, dlog_pcbp_t, void * ); |
| static void tsi_cmd_parse( char * ); |
| static dlog_ucbp_t tsi_cmd_match( void ); |
| |
| #define PREAMBLEMAX 3 |
| #define ACKMAX 3 |
| #define POSTAMBLEMAX 3 |
| |
| bool_t |
| tree_subtree_inter( void ) |
| { |
| fold_t fold; |
| char *preamblestr[ PREAMBLEMAX ]; |
| size_t preamblecnt; |
| char *ackstr[ ACKMAX ]; |
| size_t ackcnt; |
| char *postamblestr[ POSTAMBLEMAX ]; |
| size_t postamblecnt; |
| |
| dlog_ucbp_t cmdp; |
| |
| restart: |
| /* make the current working directory the root of the fs |
| */ |
| tranp->t_inter.i_cwdh = persp->p_rooth; |
| |
| /* begin the dialog |
| */ |
| preamblecnt = 0; |
| fold_init( fold, _("subtree selection dialog"), '=' ); |
| preamblestr[ preamblecnt++ ] = "\n"; |
| preamblestr[ preamblecnt++ ] = fold; |
| preamblestr[ preamblecnt++ ] = "\n\n"; |
| assert( preamblecnt <= PREAMBLEMAX ); |
| dlog_begin( preamblestr, preamblecnt ); |
| |
| /* execute commands until time to extract or quit. always begin with |
| * an implicit instructions command. if see SIGINT, give main thread |
| * dialog a chance to decide if a session interrupt is wanted. |
| */ |
| cmdp = tsi_cmd_inst; |
| do { |
| char buf[ MAXPATHLEN ]; |
| const ix_t abortix = 1; |
| const ix_t sigintix = 2; |
| const ix_t okix = 3; |
| ix_t responseix; |
| |
| /* execute command and get response |
| */ |
| responseix = dlog_string_query( cmdp, |
| 0, |
| buf, |
| sizeof( buf ), |
| 0, |
| IXMAX, /* timeout ix */ |
| sigintix, /* sigint ix */ |
| abortix, /* sighup ix */ |
| abortix, /* sigquit ix */ |
| okix ); /* ok ix */ |
| |
| /* ack the response |
| */ |
| ackcnt = 0; |
| if ( responseix == sigintix ) { |
| ackstr[ ackcnt++ ] = _("keyboard interrupt\n"); |
| } else if ( responseix == abortix ) { |
| ackstr[ ackcnt++ ] = _("abort\n"); |
| } else { |
| assert( responseix == okix ); |
| } |
| assert( ackcnt <= ACKMAX ); |
| dlog_string_ack( ackstr, |
| ackcnt ); |
| |
| /* exception handling |
| */ |
| if ( responseix != okix ) { |
| /* if exception, end the dialog. may restart |
| * if operator decidesd not to intr. |
| */ |
| postamblecnt = 0; |
| fold_init( fold, _("end dialog"), '-' ); |
| postamblestr[ postamblecnt++ ] = "\n"; |
| postamblestr[ postamblecnt++ ] = fold; |
| postamblestr[ postamblecnt++ ] = "\n\n"; |
| assert( postamblecnt <= POSTAMBLEMAX ); |
| dlog_end( postamblestr, postamblecnt ); |
| |
| /* if sighup or sigquit, immediately quit |
| */ |
| if ( responseix == abortix ) { |
| return BOOL_FALSE; |
| } |
| |
| /* if sigint, allow main thread to decide if |
| * operator really wants to quit |
| */ |
| assert( responseix == sigintix ); |
| if ( cldmgr_stop_requested( )) { |
| return BOOL_FALSE; |
| } |
| sleep( 1 ); /* to allow main thread to begin dialog */ |
| mlog( MLOG_NORMAL | MLOG_BARE | MLOG_TREE, |
| "" ); /* block until main thread dialog done */ |
| sleep( 1 ); /* let main thread ask children to die */ |
| if ( cldmgr_stop_requested( )) { |
| return BOOL_FALSE; |
| } |
| mlog( MLOG_DEBUG | MLOG_TREE, |
| "retrying interactive subtree selection dialog\n" ); |
| goto restart; |
| } |
| |
| |
| tsi_cmd_parse( buf ); |
| cmdp = tsi_cmd_match( ); |
| if ( ! cmdp ) { |
| cmdp = tsi_cmd_help; |
| } |
| } while ( cmdp != tsi_cmd_quit && cmdp != tsi_cmd_extract ); |
| |
| postamblecnt = 0; |
| fold_init( fold, _("end dialog"), '-' ); |
| postamblestr[ postamblecnt++ ] = "\n"; |
| postamblestr[ postamblecnt++ ] = fold; |
| postamblestr[ postamblecnt++ ] = "\n\n"; |
| assert( postamblecnt <= POSTAMBLEMAX ); |
| dlog_end( postamblestr, postamblecnt ); |
| |
| /* pv 773569 - quit is not a reason to consider session |
| * to be interrupted (we haven't started yet) so just unmark |
| * any selected directories and return */ |
| if ( cmdp == tsi_cmd_quit ) { |
| mlog( MLOG_NORMAL, _("Unmark and quit\n") ); |
| selsubtree( persp->p_rooth , BOOL_FALSE ); |
| } |
| |
| return BOOL_TRUE; |
| } |
| |
| static void |
| tsi_cmd_inst( void *ctxp, |
| dlog_pcbp_t pcb, |
| void *pctxp ) |
| { |
| tsi_cmd_help( ctxp, pcb, pctxp ); |
| } |
| |
| static void |
| tsi_cmd_pwd( void *ctxp, |
| dlog_pcbp_t pcb, |
| void *pctxp ) |
| { |
| /* special case root |
| */ |
| if ( tranp->t_inter.i_cwdh == persp->p_rooth ) { |
| ( * pcb )( pctxp, "cwd is fs root\n" ); |
| return; |
| } |
| |
| /* ascend tree recursively, print path on way back |
| */ |
| tsi_cmd_pwd_recurse( ctxp, pcb, pctxp, tranp->t_inter.i_cwdh ); |
| ( * pcb )( pctxp, "\n" ); |
| } |
| |
| static void |
| tsi_cmd_pwd_recurse( void *ctxp, |
| dlog_pcbp_t pcb, |
| void *pctxp, |
| nh_t nh ) |
| { |
| node_t *np; |
| register nh_t parh; |
| /* REFERENCED */ |
| register int namelen; |
| nrh_t nrh; |
| |
| assert( nh != NH_NULL ); |
| |
| np = Node_map( nh ); |
| nrh = np->n_nrh; |
| parh = np->n_parh; |
| Node_unmap( nh, &np ); |
| if ( parh != persp->p_rooth ) { |
| tsi_cmd_pwd_recurse( ctxp, pcb, pctxp, parh ); |
| /* RECURSION */ |
| ( * pcb )( pctxp, "/" ); |
| } |
| assert( nrh != NRH_NULL ); |
| namelen = namreg_get( nrh, |
| tranp->t_inter.i_name, |
| sizeof( tranp->t_inter.i_name )); |
| assert( namelen > 0 ); |
| ( * pcb )( pctxp, tranp->t_inter.i_name ); |
| } |
| |
| /* ARGSUSED */ |
| static void |
| tsi_cmd_ls( void *ctxp, |
| dlog_pcbp_t pcb, |
| void *pctxp ) |
| { |
| size_t argc = tranp->t_inter.i_argc; |
| char *arg = ( argc > 1 ) ? tranp->t_inter.i_argv[ 1 ] : 0; |
| bool_t ok; |
| |
| nh_t cldh; |
| nh_t parh; |
| nh_t namedh; |
| xfs_ino_t ino; |
| bool_t isdirpr; |
| bool_t isselpr; |
| |
| /* walk the tree according to the path argument, to get |
| * the named node. |
| */ |
| ok = tsi_walkpath( arg, |
| persp->p_rooth, |
| tranp->t_inter.i_cwdh, |
| pcb, |
| pctxp, |
| &namedh, |
| &parh, |
| &cldh, |
| &ino, |
| &isdirpr, |
| &isselpr ); |
| if ( ! ok ) { |
| return; |
| } |
| |
| /* if named is not a dir, just display named |
| */ |
| if ( ! isdirpr ) { |
| ( * pcb )( pctxp, |
| " %s %10llu %s%s\n", |
| isselpr ? "*" : " ", |
| ino, |
| tranp->t_inter.i_name, |
| isdirpr ? "/" : " " ); |
| |
| return; |
| } |
| |
| /* iterate through the directory, printing all matching entries. |
| * hide the orphanage. |
| */ |
| while ( cldh != NH_NULL ) { |
| node_t *cldp; |
| nrh_t nrh; |
| nh_t nextcldh; |
| cldp = Node_map( cldh ); |
| nrh = cldp->n_nrh; |
| nextcldh = cldp->n_sibh; |
| isdirpr = ( cldp->n_flags & NF_ISDIR ); |
| isselpr = ( cldp->n_flags & NF_SUBTREE ); |
| ino = cldp->n_ino; |
| Node_unmap( cldh, &cldp ); |
| if ( cldh != persp->p_orphh ) { |
| /* REFERENCED */ |
| int namelen; |
| namelen = namreg_get( nrh, |
| tranp->t_inter.i_name, |
| sizeof( tranp->t_inter.i_name )); |
| assert( namelen > 0 ); |
| ( * pcb )( pctxp, |
| " %s %10llu %s%s\n", |
| isselpr ? "*" : " ", |
| ino, |
| tranp->t_inter.i_name, |
| isdirpr ? "/" : " " ); |
| } |
| cldh = nextcldh; |
| } |
| } |
| |
| /* ARGSUSED */ |
| static void |
| tsi_cmd_cd( void *ctxp, |
| dlog_pcbp_t pcb, |
| void *pctxp ) |
| { |
| size_t argc = tranp->t_inter.i_argc; |
| char *arg = ( argc > 1 ) ? tranp->t_inter.i_argv[ 1 ] : 0; |
| |
| nh_t cldh; |
| nh_t parh; |
| nh_t namedh; |
| xfs_ino_t ino; |
| bool_t isdirpr; |
| bool_t isselpr; |
| bool_t ok; |
| |
| /* walk the tree according to the path argument, to get |
| * the named node. |
| */ |
| ok = tsi_walkpath( arg, |
| persp->p_rooth, |
| tranp->t_inter.i_cwdh, |
| pcb, |
| pctxp, |
| &namedh, |
| &parh, |
| &cldh, |
| &ino, |
| &isdirpr, |
| &isselpr ); |
| if ( ! ok ) { |
| return; |
| } |
| |
| /* if named is not a dir, complain |
| */ |
| if ( ! isdirpr ) { |
| assert( arg ); |
| ( * pcb )( pctxp, |
| _("%s is not a directory\n"), |
| arg ); |
| |
| return; |
| } |
| |
| /* change the current working directory |
| */ |
| tranp->t_inter.i_cwdh = namedh; |
| } |
| |
| /* ARGSUSED */ |
| static void |
| tsi_cmd_add( void *ctxp, |
| dlog_pcbp_t pcb, |
| void *pctxp ) |
| { |
| size_t argc = tranp->t_inter.i_argc; |
| char *arg = ( argc > 1 ) ? tranp->t_inter.i_argv[ 1 ] : 0; |
| |
| nh_t cldh; |
| nh_t parh; |
| nh_t namedh; |
| xfs_ino_t ino; |
| bool_t isdirpr; |
| bool_t isselpr; |
| bool_t ok; |
| |
| /* walk the tree according to the path argument, to get |
| * the named node. |
| */ |
| ok = tsi_walkpath( arg, |
| persp->p_rooth, |
| tranp->t_inter.i_cwdh, |
| pcb, |
| pctxp, |
| &namedh, |
| &parh, |
| &cldh, |
| &ino, |
| &isdirpr, |
| &isselpr ); |
| if ( ! ok ) { |
| return; |
| } |
| |
| selsubtree( namedh, BOOL_TRUE ); |
| } |
| |
| /* ARGSUSED */ |
| static void |
| tsi_cmd_delete( void *ctxp, |
| dlog_pcbp_t pcb, |
| void *pctxp ) |
| { |
| size_t argc = tranp->t_inter.i_argc; |
| char *arg = ( argc > 1 ) ? tranp->t_inter.i_argv[ 1 ] : 0; |
| |
| nh_t cldh; |
| nh_t parh; |
| nh_t namedh; |
| xfs_ino_t ino; |
| bool_t isdirpr; |
| bool_t isselpr; |
| bool_t ok; |
| |
| /* walk the tree according to the path argument, to get |
| * the named node. |
| */ |
| ok = tsi_walkpath( arg, |
| persp->p_rooth, |
| tranp->t_inter.i_cwdh, |
| pcb, |
| pctxp, |
| &namedh, |
| &parh, |
| &cldh, |
| &ino, |
| &isdirpr, |
| &isselpr ); |
| if ( ! ok ) { |
| return; |
| } |
| |
| selsubtree( namedh, BOOL_FALSE ); |
| } |
| |
| /* ARGSUSED */ |
| static void |
| tsi_cmd_extract( void *ctxp, |
| dlog_pcbp_t pcb, |
| void *pctxp ) |
| { |
| } |
| |
| /* ARGSUSED */ |
| static void |
| tsi_cmd_quit( void *ctxp, |
| dlog_pcbp_t pcb, |
| void *pctxp ) |
| { |
| } |
| |
| static int parse( int slotcnt, char **slotbuf, char *string ); |
| |
| static void |
| tsi_cmd_parse( char *buf ) |
| { |
| int wordcnt; |
| |
| if ( ! buf ) { |
| tranp->t_inter.i_argc = 0; |
| return; |
| } |
| |
| wordcnt = parse( INTER_ARGMAX, tranp->t_inter.i_argv, buf ); |
| |
| tranp->t_inter.i_argc = ( size_t )min( max( 0, wordcnt ), INTER_ARGMAX ); |
| } |
| |
| struct tsi_cmd_tbl { |
| char *tct_pattern; |
| char *tct_help; |
| dlog_ucbp_t tct_ucb; |
| size_t tct_argcmin; |
| size_t tct_argcmax; |
| }; |
| |
| typedef struct tsi_cmd_tbl tsi_cmd_tbl_t; |
| |
| static tsi_cmd_tbl_t tsi_cmd_tbl[] = { |
| { "pwd", "", tsi_cmd_pwd, 1, 1 }, |
| { "ls", "[ <path> ]", tsi_cmd_ls, 1, 2 }, |
| { "cd", "[ <path> ]", tsi_cmd_cd, 1, 2 }, |
| { "add", "[ <path> ]", tsi_cmd_add, 1, 2 }, |
| { "delete", "[ <path> ]", tsi_cmd_delete, 1, 2 }, |
| { "extract", "", tsi_cmd_extract,1, 1 }, |
| { "quit", "", tsi_cmd_quit, 1, 1 }, |
| { "help", "", tsi_cmd_help, 1, 1 }, |
| }; |
| |
| static dlog_ucbp_t |
| tsi_cmd_match( void ) |
| { |
| tsi_cmd_tbl_t *tblp = tsi_cmd_tbl; |
| tsi_cmd_tbl_t *tblendp = tsi_cmd_tbl |
| + |
| sizeof( tsi_cmd_tbl ) |
| / |
| sizeof( tsi_cmd_tbl[ 0 ] ); |
| |
| if ( tranp->t_inter.i_argc == 0 ) { |
| return 0; |
| } |
| |
| for ( ; tblp < tblendp ; tblp++ ) { |
| if ( ! strncmp( tranp->t_inter.i_argv[ 0 ], |
| tblp->tct_pattern, |
| strlen( tranp->t_inter.i_argv[ 0 ] ))) { |
| break; |
| } |
| } |
| |
| if ( tblp == tblendp ) { |
| return 0; |
| } |
| |
| assert( tblp->tct_argcmin != 0 ); |
| if ( tranp->t_inter.i_argc < tblp->tct_argcmin ) { |
| return 0; |
| } |
| |
| if ( tranp->t_inter.i_argc > tblp->tct_argcmax ) { |
| return 0; |
| } |
| |
| return tblp->tct_ucb; |
| } |
| |
| /* ARGSUSED */ |
| static void |
| tsi_cmd_help( void *ctxp, |
| dlog_pcbp_t pcb, |
| void *pctxp ) |
| { |
| tsi_cmd_tbl_t *tblp = tsi_cmd_tbl; |
| tsi_cmd_tbl_t *tblendp = tsi_cmd_tbl |
| + |
| sizeof( tsi_cmd_tbl ) |
| / |
| sizeof( tsi_cmd_tbl[ 0 ] ); |
| |
| ( * pcb )( pctxp, _("the following commands are available:\n") ); |
| for ( ; tblp < tblendp ; tblp++ ) { |
| ( * pcb )( pctxp, |
| "\t%s %s\n", |
| tblp->tct_pattern, |
| tblp->tct_help ); |
| } |
| } |
| |
| /* walks the tree following the given path. |
| * returns FALSE if syntax error encountered. |
| * returns by reference handles to the named node, its parent, |
| * the first child in its cld list, its ino, if it is a directory, |
| * and if it is selected. |
| * optionally given a dlog print func and context, to be used for diag output. |
| */ |
| static bool_t |
| tsi_walkpath( char *arg, nh_t rooth, nh_t cwdh, |
| dlog_pcbp_t pcb, void *pctxp, |
| nh_t *namedhp, nh_t *parhp, nh_t *cldhp, |
| xfs_ino_t *inop, bool_t *isdirprp, bool_t *isselprp ) |
| { |
| nh_t namedh; |
| nh_t parh; |
| nh_t cldh; |
| node_t *namedp; |
| char *path; |
| char nbuf[ NAME_MAX + 1 ]; |
| xfs_ino_t ino; |
| bool_t isdirpr; |
| bool_t isselpr; |
| |
| |
| /* correct arg if ends with slash (if arg is "/", ok) |
| */ |
| if ( arg && strlen( arg ) > 1 && arg[ strlen( arg ) - 1 ] == '/' ) { |
| arg[ strlen( arg ) - 1 ] = 0; |
| } |
| |
| /* use path to walk down the path argument |
| */ |
| path = arg; |
| |
| /* walk the tree beginning either at the root node |
| * or at the current working directory |
| */ |
| if ( path && *path == '/' ) { |
| assert( rooth != NH_NULL ); |
| namedh = rooth; |
| path++; |
| } else { |
| assert( cwdh != NH_NULL ); |
| namedh = cwdh; |
| } |
| |
| /* get the parent of the starting point, and its cld list |
| */ |
| namedp = Node_map( namedh ); |
| parh = namedp->n_parh; |
| cldh = namedp->n_cldh; |
| ino = namedp->n_ino; |
| isselpr = ( namedp->n_flags & NF_SUBTREE ); |
| assert( namedp->n_flags & NF_ISDIR ); |
| Node_unmap( namedh, &namedp ); |
| isdirpr = BOOL_TRUE; |
| |
| /* walk the tree from the starting point following the path arg. |
| * on leaving this loop, the following will be valid: |
| * namedh - the handle of the node named by path arg; |
| * parh - the parent of the named node; |
| * isdirpr - TRUE if named node is a directory; |
| * cldh - the first child in the named node's cld list. |
| */ |
| for ( ; ; ) { |
| size_t namelen; |
| char *strpatchp; |
| nh_t sibh; |
| |
| /* if no path arg, break |
| */ |
| if ( ! path ) { |
| break; |
| } |
| |
| /* clean out leading slashes. these can occur if the |
| * path arg is ".../////..." or "////..." |
| */ |
| while ( *path == '/' ) { |
| path++; |
| } |
| |
| /* if empty path arg, break |
| */ |
| if ( ! strlen( path )) { |
| break; |
| } |
| |
| /* copy the first name from the path, and advance |
| * the path pointer. |
| */ |
| namelen = strcspn( path, "/" ); |
| assert( namelen < sizeof( nbuf )); |
| strncpy( nbuf, path, namelen ); |
| nbuf[ namelen ] = 0; |
| path += namelen; |
| if ( *path ) { |
| assert( *path == '/' ); |
| strpatchp = path; |
| *strpatchp = 0; |
| path++; |
| } else { |
| strpatchp = 0; |
| } |
| |
| /* be sure the named node is a dir |
| */ |
| if ( ! isdirpr ) { |
| if ( pcb ) { |
| ( * pcb )( pctxp, _( |
| "parent of %s is not a directory\n"), |
| arg ); |
| } |
| return BOOL_FALSE; |
| } |
| |
| /* special case "." |
| */ |
| if ( ! strcmp( nbuf, "." )) { |
| if ( strpatchp ) { |
| *strpatchp = '/'; |
| } |
| continue; |
| } |
| |
| /* special case ".." |
| */ |
| if ( ! strcmp( nbuf, ".." )) { |
| if ( parh == NH_NULL ) { |
| if ( pcb ) { |
| ( * pcb )( pctxp, _( |
| "%s above root\n"), |
| arg ); |
| } |
| return BOOL_FALSE; |
| } |
| namedh = parh; |
| namedp = Node_map( namedh ); |
| parh = namedp->n_parh; |
| cldh = namedp->n_cldh; |
| ino = namedp->n_ino; |
| isselpr = ( namedp->n_flags & NF_SUBTREE ); |
| Node_unmap( namedh, &namedp ); |
| if ( strpatchp ) { |
| *strpatchp = '/'; |
| } |
| continue; |
| } |
| |
| /* look for child with right name |
| */ |
| sibh = cldh; |
| while ( sibh != NH_NULL ) { |
| node_t *sibp; |
| nh_t nextsibh; |
| nrh_t nrh; |
| /* REFERENCED */ |
| int siblen; |
| |
| sibp = Node_map( sibh ); |
| nrh = sibp->n_nrh; |
| nextsibh = sibp->n_sibh; |
| cldh = sibp->n_cldh; |
| ino = sibp->n_ino; |
| isselpr = ( sibp->n_flags & NF_SUBTREE ); |
| isdirpr = ( sibp->n_flags & NF_ISDIR ); |
| Node_unmap( sibh, &sibp ); |
| assert( nrh != NRH_NULL || sibh == persp->p_orphh ); |
| if ( nrh != NRH_NULL ) { |
| siblen = namreg_get( nrh, |
| tranp->t_inter.i_name, |
| sizeof( tranp->t_inter.i_name )); |
| assert( siblen > 0 ); |
| if ( ! strcmp( nbuf, tranp->t_inter.i_name )) { |
| break; |
| } |
| } |
| sibh = nextsibh; |
| } |
| |
| /* if no match, complain |
| */ |
| if ( sibh == NH_NULL ) { |
| if ( pcb ) { |
| ( * pcb )( pctxp, _( |
| "%s not found\n"), |
| arg ); |
| } |
| return BOOL_FALSE; |
| } |
| |
| /* continue search. cldh, ino and isdirpr |
| * set in inner loop above |
| */ |
| parh = namedh; |
| namedh = sibh; |
| if ( strpatchp ) { |
| *strpatchp = '/'; |
| } |
| } |
| *namedhp = namedh; |
| *parhp = parh; |
| *cldhp = cldh; |
| *inop = ino; |
| *isdirprp = isdirpr; |
| *isselprp = isselpr; |
| |
| return BOOL_TRUE; |
| } |
| |
| /* Node abstraction *********************************************************/ |
| |
| static nh_t |
| Node_alloc( xfs_ino_t ino, gen_t gen, nrh_t nrh, dah_t dah, size_t flags ) |
| { |
| nh_t nh; |
| node_t *np; |
| |
| nh = node_alloc( ); |
| if (nh == NH_NULL) |
| return NH_NULL; |
| np = Node_map( nh ); |
| np->n_ino = ino; |
| np->n_nrh = nrh; |
| np->n_dah = dah; |
| np->n_hashh = NH_NULL; |
| np->n_parh = NH_NULL; |
| np->n_sibh = NH_NULL; |
| np->n_sibprevh = NH_NULL; |
| np->n_cldh = NH_NULL; |
| np->n_lnkh = NH_NULL; |
| np->n_gen = gen; |
| np->n_flags = ( u_char_t )flags; |
| memset(np->n_pad, 0, sizeof(np->n_pad)); |
| Node_unmap( nh, &np ); |
| return nh; |
| } |
| |
| static void |
| Node_free( nh_t *nhp ) |
| { |
| node_t *np; |
| np = Node_map( *nhp ); |
| np->n_ino = 0; |
| np->n_gen = 0; |
| if ( np->n_nrh != NRH_NULL ) { |
| namreg_del( np->n_nrh ); |
| np->n_nrh = NRH_NULL; |
| } |
| if ( np->n_dah != DAH_NULL ) { |
| dirattr_del( np->n_dah ); |
| np->n_dah = DAH_NULL; |
| } |
| np->n_flags = 0; |
| np->n_parh = NH_NULL; |
| np->n_sibh = NH_NULL; |
| np->n_sibprevh = NH_NULL; |
| np->n_cldh = NH_NULL; |
| np->n_lnkh = NH_NULL; |
| Node_unmap( *nhp, &np ); |
| node_free( nhp ); |
| } |
| |
| /* |
| * NOTE: Does error handling here and exits. |
| */ |
| static node_t * |
| Node_map( nh_t nh ) |
| { |
| node_t *n = ( node_t * )node_map( nh ); |
| if ( n == NULL ) { |
| mlog( MLOG_ERROR | MLOG_TREE, _( |
| "failed to map in node (node handle: %u)\n"), nh); |
| exit(EXIT_ERROR); |
| } |
| return n; |
| } |
| |
| static void |
| Node_unmap( nh_t nh, node_t **npp ) |
| { |
| node_unmap( nh, ( void ** )npp ); |
| } |
| |
| /* builds a pathname for the specified node, relative to root |
| * returns FALSE if pathname too long |
| */ |
| static bool_t |
| Node2path( nh_t nh, char *path, char *errmsg ) |
| { |
| int remainingcnt; |
| strcpy(path, "."); /* in case root node passed in */ |
| remainingcnt = Node2path_recurse( nh, path, MAXPATHLEN, 0 ); |
| if ( remainingcnt <= 0 ) { |
| node_t *np = Node_map( nh ); |
| xfs_ino_t ino = np->n_ino; |
| gen_t gen = np->n_gen; |
| Node_unmap( nh, &np ); |
| mlog( MLOG_NORMAL | MLOG_WARNING | MLOG_TREE, _( |
| "unable %s ino %llu gen %u: " |
| "relative pathname too long (partial %s)\n"), |
| errmsg, |
| ino, |
| gen, |
| path ); |
| return BOOL_FALSE; |
| } else { |
| return BOOL_TRUE; |
| } |
| } |
| |
| /* returns how much of the buffer remains, assuming the buffer size is |
| * MAXPATHLEN. always null-terminates, but null char not counted in return. |
| * works because the buffer size is secretly 2 * MAXPATHLEN. |
| */ |
| static int |
| Node2path_recurse( nh_t nh, char *buf, int bufsz, int level ) |
| { |
| static __thread path_cache_t cache = { NH_NULL, 0, "" }; |
| node_t *np; |
| nh_t parh; |
| xfs_ino_t ino; |
| gen_t gen; |
| nrh_t nrh; |
| char *oldbuf; |
| int oldbufsz; |
| int namelen; |
| |
| /* recursion termination |
| */ |
| if ( nh == persp->p_rooth ) { |
| return bufsz; |
| } |
| |
| /* if we have a cache hit, no need to recurse any further |
| */ |
| if ( nh == cache.nh ) { |
| assert( bufsz > cache.len ); |
| strcpy( buf, cache.buf ); |
| return bufsz - cache.len; |
| } |
| |
| /* extract useful node members |
| */ |
| np = Node_map( nh ); |
| parh = np->n_parh; |
| ino = np->n_ino; |
| gen = np->n_gen; |
| nrh = np->n_nrh; |
| Node_unmap( nh, &np ); |
| |
| /* build path to parent |
| */ |
| oldbuf = buf; |
| oldbufsz = bufsz; |
| bufsz = Node2path_recurse( parh, buf, bufsz, level+1 ); /* RECURSION */ |
| if ( bufsz <= 0 ) { |
| return bufsz; |
| } |
| buf += oldbufsz - bufsz; |
| |
| /* insert slash if parent not root |
| */ |
| if ( parh != persp->p_rooth ) { |
| assert( bufsz + MAXPATHLEN >= 2 ); |
| *buf++ = '/'; |
| *( buf + 1 ) = 0; |
| bufsz--; |
| if ( bufsz <= 0 ) { |
| return bufsz; |
| } |
| } |
| |
| /* append entry name: special case if in orphanage |
| */ |
| if ( parh == persp->p_orphh ) { |
| namelen = sprintf( buf, "%llu.%u", (unsigned long long)ino, gen ); |
| } else if ( nh == persp->p_orphh ) { |
| namelen = sprintf( buf, "%s", orphname ); |
| } else { |
| assert( nrh != NRH_NULL ); |
| namelen = namreg_get( nrh, buf, ( size_t )bufsz + MAXPATHLEN ); |
| assert( namelen > 0 ); |
| } |
| |
| /* update remaining buffer size |
| */ |
| bufsz -= namelen; |
| assert( bufsz + MAXPATHLEN > 0 ); |
| |
| /* update the cache if we're the target's parent |
| * (and the pathname is not too long) |
| */ |
| if ( level == 1 && bufsz > 0 ) { |
| cache.nh = nh; |
| strcpy( cache.buf, oldbuf ); |
| cache.len = oldbufsz - bufsz; |
| } |
| |
| /* return remaining buffer size |
| */ |
| return bufsz; |
| } |
| |
| /* family abstraction *********************************************************/ |
| |
| static void |
| adopt( nh_t parh, nh_t cldh, nrh_t nrh ) |
| { |
| node_t *parp; |
| node_t *cldp; |
| node_t *sibp; |
| |
| #ifdef TREE_DEBUG |
| mlog(MLOG_DEBUG | MLOG_TREE, |
| "adopt(parent=%llu,child=%llu,node=%llu): \n", |
| parh, cldh, nrh); |
| #endif |
| |
| /* fix up our child - put at front of child list */ |
| cldp = Node_map( cldh ); |
| cldp->n_parh = parh; |
| cldp->n_nrh = nrh; |
| parp = Node_map( parh ); |
| cldp->n_sibh = parp->n_cldh; |
| cldp->n_sibprevh = NH_NULL; |
| Node_unmap( cldh, &cldp ); |
| |
| /* fix up old first child i.e. child's new sibling */ |
| if ( parp->n_cldh != NH_NULL ) { /* if parent has a child */ |
| sibp = Node_map( parp->n_cldh ); |
| sibp->n_sibprevh = cldh; |
| Node_unmap( parp->n_cldh, &sibp ); |
| } |
| |
| /* fix up parent */ |
| parp->n_cldh = cldh; |
| Node_unmap( parh, &parp ); |
| } |
| |
| static nrh_t |
| disown( nh_t cldh ) |
| { |
| node_t *cldp; |
| nrh_t nrh; |
| nh_t parh; |
| node_t *parp; |
| |
| cldp = Node_map( cldh ); |
| |
| nrh = cldp->n_nrh; |
| |
| parh = cldp->n_parh; |
| assert( parh != NH_NULL ); |
| if ( parh == NH_NULL ) { |
| mlog( MLOG_NORMAL | MLOG_WARNING | MLOG_TREE, _( |
| "attempt to disown child " |
| "which has no parent!\n") ); |
| return nrh; |
| } |
| parp = Node_map( parh ); |
| assert( parp->n_cldh != NH_NULL ); |
| if ( parp->n_cldh == NH_NULL ) { |
| mlog( MLOG_NORMAL | MLOG_WARNING | MLOG_TREE, _( |
| "attempt to disown child " |
| "when parent has no children!\n") ); |
| return nrh; |
| } |
| if ( parp->n_cldh == cldh ) { |
| /* child is the first one in the child list */ |
| parp->n_cldh = cldp->n_sibh; |
| if ( cldp->n_sibh != NH_NULL ) { |
| node_t *sibp = Node_map( cldp->n_sibh ); |
| sibp->n_sibprevh = NH_NULL; |
| Node_unmap( cldp->n_sibh, &sibp ); |
| } |
| } else { |
| /* child is further down the child list */ |
| /* use double link list to find previous link */ |
| nh_t prevcldh = cldp->n_sibprevh; |
| node_t *prevcldp; |
| |
| assert(prevcldh != NH_NULL); /* must be a previous */ |
| prevcldp = Node_map( prevcldh ); |
| |
| /* fix up previous */ |
| prevcldp->n_sibh = cldp->n_sibh; |
| Node_unmap( prevcldh, &prevcldp ); |
| |
| /* fix up next */ |
| if ( cldp->n_sibh != NH_NULL ) { |
| node_t *sibp = Node_map( cldp->n_sibh ); |
| sibp->n_sibprevh = prevcldh; |
| Node_unmap( cldp->n_sibh, &sibp ); |
| } |
| } |
| Node_unmap( parh, &parp ); |
| cldp->n_parh = NH_NULL; |
| cldp->n_sibh = NH_NULL; |
| cldp->n_sibprevh = NH_NULL; |
| Node_unmap( cldh, &cldp ); |
| |
| return nrh; |
| } |
| |
| /* recursively marks all nodes in subtree as selected or not selected |
| * for subtree restoral. adjusts ancestors flags accordingly. also adjusts |
| * inomap, which will be used by content.c to see if a media file contains |
| * any nondirs which might need to be restored. |
| */ |
| static void |
| selsubtree( nh_t nh, bool_t sensepr ) |
| { |
| node_t *np; |
| nh_t parh; |
| |
| /* first mark the subtree |
| */ |
| selsubtree_recurse_down( nh, sensepr ); |
| |
| /* get parent |
| */ |
| np = Node_map( nh ); |
| parh = np->n_parh; |
| Node_unmap( nh, &np ); |
| |
| /* next adjust ancestory |
| */ |
| while ( parh != NH_NULL ) { |
| node_t *parp; |
| nh_t newparh; |
| |
| parp = Node_map( parh ); |
| if ( sensepr ) { |
| parp->n_flags |= NF_SUBTREE; |
| } else { |
| bool_t atleastonechildselpr = BOOL_FALSE; |
| nh_t cldh = parp->n_cldh; |
| while ( cldh != NH_NULL ) { |
| node_t *cldp; |
| nh_t nextcldh; |
| bool_t cldsensepr; |
| cldp = Node_map( cldh ); |
| cldsensepr = ( cldp->n_flags & NF_SUBTREE ) |
| ? |
| BOOL_TRUE |
| : |
| BOOL_FALSE; |
| nextcldh = cldp->n_sibh; |
| Node_unmap( cldh, &cldp ); |
| if ( cldsensepr ) { |
| atleastonechildselpr = BOOL_TRUE; |
| break; |
| } |
| cldh = nextcldh; |
| } |
| if ( ! atleastonechildselpr ) { |
| parp->n_flags &= ~NF_SUBTREE; |
| /* DBG could break out here (remember to unmap!) |
| */ |
| } |
| } |
| newparh = parp->n_parh; |
| Node_unmap( parh, &parp ); |
| parh = newparh; |
| } |
| } |
| |
| static void |
| selsubtree_recurse_down( nh_t nh, bool_t sensepr ) |
| { |
| nh_t cldh; |
| |
| /* first mark the node indicated, and get head of cld list |
| */ |
| { |
| node_t *np; |
| bool_t isdirpr; |
| xfs_ino_t ino; |
| gen_t gen; |
| |
| np = Node_map( nh ); |
| if ( sensepr ) { |
| np->n_flags |= NF_SUBTREE; |
| } else { |
| np->n_flags &= ~NF_SUBTREE; |
| } |
| cldh = np->n_cldh; |
| ino = np->n_ino; |
| gen = np->n_gen; |
| isdirpr = ( np->n_flags & NF_ISDIR ); |
| Node_unmap( nh, &np ); |
| if ( ! isdirpr ) { |
| if ( sensepr ) { |
| inomap_rst_add( ino ); |
| } else { |
| /* check hardlist: don't del unless none needed |
| */ |
| nh_t nh; |
| bool_t neededpr = BOOL_FALSE; |
| for ( nh = link_hardh( ino, gen ) |
| ; |
| nh != NH_NULL |
| ; |
| nh = link_nexth( nh )) { |
| node_t *np; |
| u_char_t flags; |
| np = Node_map( nh ); |
| flags = np->n_flags; |
| Node_unmap( nh, &np ); |
| if ( flags & NF_SUBTREE ) { |
| neededpr = BOOL_TRUE; |
| break; |
| } |
| } |
| if ( ! neededpr ) { |
| inomap_rst_del( ino ); |
| } |
| } |
| } |
| } |
| |
| /* then mark all of its children. be sure to skip the orphanage!!! |
| */ |
| while ( cldh != NH_NULL ) { |
| node_t *cldp; |
| nh_t nextcldh; |
| |
| if ( cldh != persp->p_orphh ) { |
| selsubtree_recurse_down( cldh, sensepr ); |
| } |
| cldp = Node_map( cldh ); |
| nextcldh = cldp->n_sibh; |
| Node_unmap( cldh, &cldp ); |
| cldh = nextcldh; |
| } |
| } |
| |
| |
| /* link abstraction *********************************************************/ |
| |
| /* returns handle to head of hard link list |
| */ |
| static nh_t |
| link_hardh( xfs_ino_t ino, gen_t gen ) |
| { |
| 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 */ |
| int namelen; |
| namelen = namreg_get( np->n_nrh, |
| tranp->t_namebuf, |
| sizeof( tranp->t_namebuf )); |
| assert( namelen > 0 ); |
| if ( ! strcmp( name, tranp->t_namebuf )) { |
| Node_unmap( hardh, &np ); |
| break; |
| } |
| } |
| nexth = np->n_lnkh; |
| Node_unmap( hardh, &np ); |
| hardh = nexth; |
| } |
| return hardh; |
| } |
| |
| static void |
| link_in( nh_t nh ) |
| { |
| node_t *np; |
| xfs_ino_t ino; |
| gen_t gen; |
| nh_t hardh; |
| |
| #ifdef TREE_DEBUG |
| mlog(MLOG_DEBUG | MLOG_TREE, |
| "link_in(%llu): map in node\n", nh); |
| #endif |
| |
| /* map in the node to read ino and gen |
| */ |
| np = Node_map( nh ); |
| ino = np->n_ino; |
| gen = np->n_gen; |
| |
| /* see if one or more links already hashed. |
| */ |
| hardh = hash_find( ino, gen ); |
| |
| /* if not hashed, just hash it. otherwise put at end |
| * of hard link (lnk) list. |
| */ |
| if ( hardh == NH_NULL ) { |
| #ifdef TREE_DEBUG |
| mlog(MLOG_DEBUG | MLOG_TREE, |
| "link_in(): hash node in for ino %llu\n", ino); |
| #endif |
| hash_in( nh ); |
| } else { |
| nh_t prevh = hardh; |
| node_t *prevp = Node_map( prevh ); |
| #ifdef TREE_DEBUG |
| mlog(MLOG_DEBUG | MLOG_TREE, |
| "link_in(): put at end of hash list\n"); |
| #endif |
| while ( prevp->n_lnkh != NH_NULL ) { |
| nh_t nexth = prevp->n_lnkh; |
| Node_unmap( prevh, &prevp ); |
| prevh = nexth; |
| prevp = Node_map( prevh ); |
| } |
| prevp->n_lnkh = nh; |
| Node_unmap( prevh, &prevp ); |
| } |
| |
| /* since always put at end of hard link list, make node's |
| * lnk member terminate list. |
| */ |
| np->n_lnkh = NH_NULL; |
| Node_unmap( nh, &np ); |
| #ifdef TREE_DEBUG |
| mlog(MLOG_DEBUG | MLOG_TREE, |
| "link_in(%llu): UNmap in node\n", nh); |
| #endif |
| } |
| |
| static void |
| link_out( nh_t nh ) |
| { |
| node_t *np; |
| xfs_ino_t ino; |
| gen_t gen; |
| nh_t hardh; |
| |
| /* map in the node to read ino and gen |
| */ |
| np = Node_map( nh ); |
| ino = np->n_ino; |
| gen = np->n_gen; |
| |
| /* get head of hard link list |
| */ |
| hardh = hash_find( ino, gen ); |
| assert( hardh != NH_NULL ); |
| |
| /* if node is at head of hard link list, hash it out and |
| * hash in the following node in link list, if there is one. |
| * otherwise, unlink from hardlink list. |
| */ |
| if ( nh == hardh ) { |
| hash_out( nh ); |
| if ( np->n_lnkh != NH_NULL ) { |
| hash_in( np->n_lnkh ); |
| } |
| } else { |
| nh_t prevh = hardh; |
| node_t *prevp = Node_map( prevh ); |
| while ( prevp->n_lnkh != nh ) { |
| nh_t nexth = prevp->n_lnkh; |
| Node_unmap( prevh, &prevp ); |
| prevh = nexth; |
| assert( prevh != NH_NULL ); |
| prevp = Node_map( prevh ); |
| } |
| prevp->n_lnkh = np->n_lnkh; |
| Node_unmap( prevh, &prevp ); |
| } |
| np->n_lnkh = NH_NULL; |
| |
| /* release the mapping |
| */ |
| Node_unmap( nh, &np ); |
| } |
| |
| /* invokes callback for all hardheads |
| * iteration aborted if callback returns FALSE |
| */ |
| static void |
| link_headiter( bool_t ( * cbfp )( void *contextp, nh_t hardh ), void *contextp ) |
| { |
| hash_iter( cbfp, contextp ); |
| } |
| |
| /* iterator for a hard link list. allows deletion of the last node |
| * returned. |
| */ |
| static void |
| link_iter_init( link_iter_context_t *link_iter_contextp, nh_t hardheadh ) |
| { |
| link_iter_contextp->li_headh = hardheadh; |
| link_iter_contextp->li_prevh = NH_NULL; |
| link_iter_contextp->li_lasth = NH_NULL; |
| link_iter_contextp->li_donepr = BOOL_FALSE; |
| } |
| |
| static nh_t |
| link_iter_next( link_iter_context_t *link_iter_contextp ) |
| { |
| node_t *lastp; |
| nh_t tmplasth; |
| |
| /* if already done, return |
| */ |
| if ( link_iter_contextp->li_donepr == BOOL_TRUE ) { |
| return NH_NULL; |
| } |
| |
| /* if no hardhead, done |
| */ |
| if ( link_iter_contextp->li_headh == NH_NULL ) { |
| link_iter_contextp->li_donepr = BOOL_TRUE; |
| return NH_NULL; |
| } |
| |
| /* make tmp copy of last |
| */ |
| tmplasth = link_iter_contextp->li_lasth; |
| |
| /* if no last, must be first call |
| */ |
| if ( tmplasth == NH_NULL ) { |
| assert( link_iter_contextp->li_prevh == NH_NULL ); |
| link_iter_contextp->li_lasth = link_iter_contextp->li_headh; |
| return link_iter_contextp->li_lasth; |
| } |
| |
| /* slide last into prev |
| */ |
| link_iter_contextp->li_prevh = tmplasth; |
| lastp = Node_map( tmplasth ); |
| link_iter_contextp->li_lasth = lastp->n_lnkh; |
| Node_unmap( tmplasth, &lastp ); |
| |
| /* if NULL, flag done |
| */ |
| if ( link_iter_contextp->li_lasth == NH_NULL ) { |
| link_iter_contextp->li_donepr = BOOL_TRUE; |
| } |
| |
| /* return the last handle |
| */ |
| return link_iter_contextp->li_lasth; |
| } |
| |
| /* ARGSUSED */ |
| void |
| link_iter_unlink( link_iter_context_t *link_iter_contextp, nh_t nh ) |
| { |
| node_t *lastp; |
| nh_t nexth; |
| |
| /* sanity checks |
| */ |
| assert( link_iter_contextp->li_lasth != NH_NULL ); |
| assert( nh == link_iter_contextp->li_lasth ); |
| |
| /* get the next node in list |
| */ |
| lastp = Node_map( link_iter_contextp->li_lasth ); |
| nexth = lastp->n_lnkh; |
| lastp->n_lnkh = NH_NULL; |
| Node_unmap( link_iter_contextp->li_lasth, &lastp ); |
| |
| if ( link_iter_contextp->li_lasth == link_iter_contextp->li_headh ) { |
| assert( link_iter_contextp->li_prevh == NH_NULL ); |
| hash_out( link_iter_contextp->li_headh ); |
| link_iter_contextp->li_headh = nexth; |
| if ( nexth != NH_NULL ) { |
| hash_in( nexth ); |
| } |
| } else { |
| node_t *prevp; |
| assert( link_iter_contextp->li_prevh != NH_NULL ); |
| prevp = Node_map( link_iter_contextp->li_prevh ); |
| prevp->n_lnkh = nexth; |
| Node_unmap( link_iter_contextp->li_prevh, &prevp ); |
| } |
| link_iter_contextp->li_lasth = link_iter_contextp->li_prevh; |
| link_iter_contextp->li_prevh = NH_NULL; |
| } |
| |
| |
| /* hash abstraction *********************************************************/ |
| |
| #define HASHLEN_MIN ( pgsz / sizeof( nh_t )) |
| |
| static bool_t |
| hash_init( size64_t vmsz, |
| size64_t dircnt, |
| size64_t nondircnt, |
| char *perspath ) |
| { |
| size64_t hashlen; |
| size64_t loghashlen; |
| size64_t vmlen; |
| size64_t hashlenmax; |
| ix_t hix; |
| |
| /* sanity checks |
| */ |
| assert( pgsz % sizeof( nh_t ) == 0 ); |
| |
| /* calculate the size of the hash array. must be a power of two, |
| * and a multiple of the page size. don't use more than the available |
| * vm. but enforce a minimum. |
| */ |
| vmlen = vmsz / sizeof( nh_t ); |
| hashlenmax = min( vmlen, SIZEMAX ); |
| hashlen = ( dircnt + nondircnt ); |
| hashlen = max( hashlen, ( size64_t )HASHLEN_MIN ); |
| hashlen = min( hashlen, hashlenmax ); |
| |
| for ( loghashlen = 0 |
| ; |
| ( ( size64_t )1 << loghashlen ) <= hashlen |
| ; |
| loghashlen++ ) |
| ; |
| assert( loghashlen > 0 ); |
| hashlen = ( size64_t )1 << loghashlen; |
| if (hashlen > hashlenmax) |
| hashlen >>= 1; |
| assert( hashlen <= hashlenmax ); |
| |
| /* record hash size in persistent state |
| */ |
| persp->p_hashsz = hashlen * sizeof( nh_t ); |
| |
| /* map the hash array just after the persistent state header |
| */ |
| assert( persp->p_hashsz <= SIZEMAX ); |
| assert( ! ( persp->p_hashsz % ( size64_t )pgsz )); |
| assert( ! ( PERSSZ % pgsz )); |
| tranp->t_hashp = ( nh_t * ) mmap_autogrow( |
| ( size_t )persp->p_hashsz, |
| tranp->t_persfd, |
| ( off64_t )PERSSZ ); |
| if ( tranp->t_hashp == ( nh_t * )-1 ) { |
| mlog( MLOG_NORMAL | MLOG_TREE, _( |
| "unable to mmap hash array into %s: %s\n"), |
| perspath, |
| strerror( errno )); |
| return BOOL_FALSE; |
| } |
| |
| /* initialize the hash array to all NULL node handles |
| */ |
| for ( hix = 0 ; hix < ( ix_t )hashlen ; hix++ ) { |
| tranp->t_hashp[ hix ] = NH_NULL; |
| } |
| |
| /* build a hash mask. this works because hashlen is a power of two. |
| * record in persistent state. |
| */ |
| assert( hashlen - 1 <= SIZEMAX ); |
| persp->p_hashmask = ( size_t )( hashlen - 1 ); |
| |
| return BOOL_TRUE; |
| } |
| |
| static bool_t |
| hash_sync( char *perspath ) |
| { |
| size64_t hashsz; |
| |
| /* sanity checks |
| */ |
| assert( pgsz % sizeof( nh_t ) == 0 ); |
| |
| /* retrieve the hash size from the persistent state |
| */ |
| hashsz = persp->p_hashsz; |
| assert( ! ( hashsz % sizeof( nh_t ))); |
| |
| /* map the hash array just after the persistent state header |
| */ |
| assert( hashsz <= SIZEMAX ); |
| assert( ! ( hashsz % ( size64_t )pgsz )); |
| assert( ! ( PERSSZ % pgsz )); |
| tranp->t_hashp = ( nh_t * ) mmap_autogrow( |
| ( size_t )hashsz, |
| tranp->t_persfd, |
| ( off64_t )PERSSZ ); |
| if ( tranp->t_hashp == ( nh_t * )-1 ) { |
| mlog( MLOG_NORMAL | MLOG_TREE, _( |
| "unable to mmap hash array into %s: %s\n"), |
| perspath, |
| strerror( errno )); |
| return BOOL_FALSE; |
| } |
| |
| return BOOL_TRUE; |
| } |
| |
| static inline size_t |
| hash_val(xfs_ino_t ino, size_t hashmask) |
| { |
| ino += ~(ino << 15); |
| ino ^= (ino >> 10); |
| ino += (ino << 3); |
| ino ^= (ino >> 6); |
| ino += ~(ino << 11); |
| ino ^= (ino >> 16); |
| return (size_t)ino & hashmask; |
| } |
| |
| static void |
| hash_in( nh_t nh ) |
| { |
| node_t *np; |
| xfs_ino_t ino; |
| size_t hix; |
| nh_t *entryp; |
| |
| /* get a mapping to the node |
| */ |
| np = Node_map( nh ); |
| |
| /* get ino from node |
| */ |
| ino = np->n_ino; |
| |
| /* assert not already in |
| */ |
| assert( hash_find( np->n_ino, np->n_gen ) == NH_NULL ); |
| |
| /* calculate the hash index |
| */ |
| hix = hash_val(ino, persp->p_hashmask); |
| |
| /* get a pointer to the indexed hash array entry |
| */ |
| entryp = &tranp->t_hashp[ hix ]; |
| |
| /* insert into the list, at the head |
| */ |
| assert( np->n_hashh == NH_NULL ); |
| np->n_hashh = *entryp; |
| *entryp = nh; |
| |
| /* release the mapping |
| */ |
| Node_unmap( nh, &np ); |
| } |
| |
| static void |
| hash_out( nh_t nh ) |
| { |
| node_t *np; |
| xfs_ino_t ino; |
| nh_t hashheadh; |
| size_t hix; |
| nh_t *entryp; |
| |
| /* get a mapping to the node |
| */ |
| np = Node_map( nh ); |
| |
| /* get the ino |
| */ |
| ino = np->n_ino; |
| |
| /* get a pointer to the hash array entry |
| */ |
| hix = hash_val(ino, persp->p_hashmask); |
| entryp = &tranp->t_hashp[ hix ]; |
| |
| /* get the handle of the first node in the appropriate hash array |
| */ |
| hashheadh = *entryp; |
| assert( hashheadh != NH_NULL ); |
| |
| /* if node is first in list, replace entry with following node. |
| * otherwise, walk the list until found. |
| */ |
| if ( hashheadh == nh ) { |
| *entryp = np->n_hashh; |
| } else { |
| nh_t prevh = hashheadh; |
| node_t *prevp = Node_map( prevh ); |
| while ( prevp->n_hashh != nh ) { |
| nh_t nexth = prevp->n_hashh; |
| Node_unmap( prevh, &prevp ); |
| prevh = nexth; |
| assert( prevh != NH_NULL ); |
| prevp = Node_map( prevh ); |
| } |
| prevp->n_hashh = np->n_hashh; |
| Node_unmap( prevh, &prevp ); |
| } |
| np->n_hashh = NH_NULL; |
| |
| /* release the mapping |
| */ |
| Node_unmap( nh, &np ); |
| } |
| |
| static nh_t |
| hash_find( xfs_ino_t ino, gen_t gen ) |
| { |
| nh_t nh; |
| node_t *np; |
| size_t hix; |
| |
| /* get handle to first node in appropriate hash array |
| */ |
| hix = hash_val(ino, persp->p_hashmask); |
| nh = tranp->t_hashp[ hix ]; |
| |
| /* if list empty, return null handle |
| */ |
| if ( nh == NH_NULL ) { |
| return NH_NULL; |
| } |
| |
| #ifdef TREE_DEBUG |
| mlog(MLOG_DEBUG | MLOG_TREE, |
| "hash_find(%llu,%u): traversing hash link list\n", |
| ino, gen); |
| #endif |
| |
| /* walk the list until found. |
| */ |
| np = Node_map( nh ); |
| while ( np->n_ino != ino || np->n_gen != gen ) { |
| nh_t nextnh = np->n_hashh; |
| Node_unmap( nh, &np ); |
| nh = nextnh; |
| if ( nh == NH_NULL ) { |
| return NH_NULL; |
| } |
| np = Node_map( nh ); |
| } |
| Node_unmap( nh, &np ); |
| |
| #ifdef TREE_DEBUG |
| mlog(MLOG_DEBUG | MLOG_TREE, |
| "hash_find(): return nh = %llu\n", nh); |
| #endif |
| return nh; |
| } |
| |
| /* invokes callback for all hashed nodes |
| * iteration aborted if callback returns FALSE |
| * call back may hash out and free the node, so |
| * must figure next node prior to calling callback. |
| */ |
| static void |
| hash_iter( bool_t ( * cbfp )( void *contextp, nh_t hashh ), void *contextp ) |
| { |
| ix_t hix; |
| size64_t hashlen = persp->p_hashsz / sizeof( nh_t ); |
| |
| for ( hix = 0 ; hix < ( ix_t )hashlen ; hix++ ) { |
| nh_t nh = tranp->t_hashp[ hix ]; |
| |
| while ( nh != NH_NULL ) { |
| node_t *np; |
| nh_t nexth; |
| bool_t ok; |
| |
| np = Node_map( nh ); |
| nexth = np->n_hashh; |
| Node_unmap( nh, &np ); |
| |
| ok = ( * cbfp )( contextp, nh ); |
| if ( ! ok ) { |
| return; |
| } |
| |
| nh = nexth; |
| } |
| } |
| } |
| |
| /* misc static functions *****************************************************/ |
| |
| #ifdef TREE_CHK |
| /* use hash array to iterate through all nodes. check |
| * each node's hash, hardlink, namreg, dirattr, parent, |
| * and sibling handles. |
| */ |
| static bool_t |
| Node_chk( nh_t nh, nh_t *nexthashhp, nh_t *nextlnkhp ) |
| { |
| node_t *np; |
| node_t n; |
| char nambuf[ NAME_MAX + 1 ]; |
| bool_t okaccum; |
| |
| mlog( MLOG_NITTY + 1 | MLOG_TREE, |
| "checking node nh == 0x%x\n", |
| nh ); |
| |
| okaccum = BOOL_TRUE; |
| |
| if ( nexthashhp ) { |
| *nexthashhp = NH_NULL; |
| } |
| |
| assert( nextlnkhp ); |
| *nextlnkhp = NH_NULL; |
| |
| np = Node_map( nh ); |
| assert( np ); |
| n = *np; |
| Node_unmap( nh, &np ); |
| |
| if ( ! nexthashhp && n.n_hashh != NH_NULL ) { |
| mlog( MLOG_NORMAL | MLOG_ERROR | MLOG_TREE, _( |
| "nh 0x%x np 0x%x hash link not null\n"), |
| nh, |
| np ); |
| okaccum = BOOL_FALSE; |
| } |
| |
| if ( n.n_hashh != NH_NULL ) { |
| np = Node_map( n.n_hashh ); |
| Node_unmap( n.n_hashh, &np ); |
| } |
| |
| if ( n.n_lnkh != NH_NULL ) { |
| np = Node_map( n.n_lnkh ); |
| Node_unmap( n.n_lnkh, &np ); |
| } |
| |
| if ( n.n_parh != NH_NULL ) { |
| np = Node_map( n.n_parh ); |
| Node_unmap( n.n_parh, &np ); |
| } |
| |
| if ( n.n_cldh != NH_NULL ) { |
| np = Node_map( n.n_cldh ); |
| Node_unmap( n.n_cldh, &np ); |
| } |
| |
| if ( n.n_sibh != NH_NULL ) { |
| np = Node_map( n.n_sibh ); |
| Node_unmap( n.n_sibh, &np ); |
| } |
| |
| if ( n.n_nrh != NRH_NULL ) { |
| int rval; |
| rval = namreg_get( n.n_nrh, nambuf, sizeof( nambuf )); |
| assert( rval >= 0 ); |
| } |
| |
| if ( n.n_dah != DAH_NULL ) { |
| ( void )dirattr_get_mode( n.n_dah ); |
| } |
| |
| if ( nexthashhp ) { |
| *nexthashhp = n.n_hashh; |
| } |
| |
| *nextlnkhp = n.n_lnkh; |
| |
| return okaccum; |
| } |
| |
| bool_t |
| tree_chk( void ) |
| { |
| ix_t hix; |
| size64_t hashlen = persp->p_hashsz / sizeof( nh_t ); |
| bool_t ok; |
| bool_t okaccum; |
| |
| okaccum = BOOL_TRUE; |
| |
| for ( hix = 0 ; hix < ( ix_t )hashlen ; hix++ ) { |
| nh_t hashh = tranp->t_hashp[ hix ]; |
| |
| mlog( MLOG_NITTY + 1 | MLOG_TREE, |
| "checking hix %u\n", |
| hix ); |
| while ( hashh != NH_NULL ) { |
| nh_t lnkh; |
| |
| ok = Node_chk( hashh, &hashh, &lnkh ); |
| if ( ! ok ) { |
| okaccum = BOOL_FALSE; |
| } |
| |
| while ( lnkh != NH_NULL ) { |
| ok = Node_chk( lnkh, 0, &lnkh ); |
| if ( ! ok ) { |
| okaccum = BOOL_FALSE; |
| } |
| } |
| } |
| } |
| |
| ok = tree_chk2( ); |
| if ( ! ok ) { |
| okaccum = BOOL_FALSE; |
| } |
| |
| return okaccum; |
| } |
| |
| static bool_t tree_chk2_recurse( nh_t cldh, nh_t parh ); |
| |
| static bool_t |
| tree_chk2( void ) |
| { |
| node_t *rootp; |
| nh_t cldh; |
| bool_t ok; |
| |
| mlog( MLOG_DEBUG | MLOG_TREE, |
| "tree hierarchy check\n" ); |
| |
| rootp = Node_map( persp->p_rooth ); |
| cldh = rootp->n_cldh; |
| Node_unmap( persp->p_rooth, &rootp ); |
| |
| ok = tree_chk2_recurse( cldh, persp->p_rooth ); |
| |
| return ok; |
| } |
| |
| static bool_t |
| tree_chk2_recurse( nh_t cldh, nh_t parh ) |
| { |
| bool_t okaccum = BOOL_TRUE; |
| |
| assert( parh != NH_NULL ); |
| |
| while ( cldh != NH_NULL ) { |
| node_t *cldp; |
| xfs_ino_t ino; |
| gen_t gen; |
| nh_t nodeparh; |
| nrh_t nrh; |
| nh_t grandcldh; |
| nh_t nextcldh; |
| bool_t ok; |
| |
| cldp = Node_map( cldh ); |
| ino = cldp->n_ino; |
| gen = cldp->n_gen; |
| nodeparh = cldp->n_parh; |
| nrh = cldp->n_nrh; |
| grandcldh = cldp->n_cldh; |
| nextcldh = cldp->n_sibh; |
| Node_unmap( cldh, &cldp ); |
| |
| if ( parh == persp->p_orphh ) { |
| sprintf( tranp->t_namebuf, "%llu.%u", ino, gen ); |
| } else if ( cldh == persp->p_orphh ) { |
| sprintf( tranp->t_namebuf, "%llu.%u", ino, gen ); |
| } else { |
| int namelen; |
| namelen = namreg_get( nrh, |
| tranp->t_namebuf, |
| sizeof( tranp->t_namebuf )); |
| assert( namelen >= 0 ); |
| } |
| |
| if ( nodeparh == NH_NULL ) { |
| mlog( MLOG_NORMAL | MLOG_WARNING | MLOG_TREE, _( |
| "node %x %s %llu %u parent NULL\n"), |
| cldh, |
| tranp->t_namebuf, |
| ino, |
| gen ); |
| return BOOL_FALSE; |
| } else if ( nodeparh != parh ) { |
| mlog( MLOG_NORMAL | MLOG_WARNING | MLOG_TREE, _( |
| "node %x %s %llu %u parent mismatch: " |
| "nodepar %x par %x\n"), |
| cldh, |
| tranp->t_namebuf, |
| ino, |
| gen, |
| nodeparh, |
| parh ); |
| return BOOL_FALSE; |
| } else { |
| mlog( MLOG_DEBUG | MLOG_TREE, |
| "node %x %s %llu %u parent %x\n", |
| cldh, |
| tranp->t_namebuf, |
| ino, |
| gen, |
| parh ); |
| } |
| ok = tree_chk2_recurse( grandcldh, cldh ); |
| if ( ! ok ) { |
| okaccum = BOOL_FALSE; |
| } |
| |
| cldh = nextcldh; |
| } |
| |
| return okaccum; |
| } |
| |
| #endif /* TREE_CHK */ |
| |
| static char *whites = " \t\r\n\v\f"; |
| |
| static int is_white( char c ); |
| static void fix_escape( char *string, char *liter ); |
| static void fix_quoted_span( char *string, char *liter ); |
| static void collapse_white( char *string, char *liter ); |
| static size_t distance_to_space( char *s, char *l ); |
| |
| static int |
| parse( int slotcnt, char **slotbuf, char *string ) |
| { |
| char *liter; |
| char *s; |
| char *l; |
| size_t wordcnt; |
| |
| /* sanity checkcs |
| */ |
| assert( slotcnt >= 0 ); |
| |
| /* allocate a companion to the input string for identifying |
| * characters which are to be interpreted literally. |
| */ |
| liter = ( char * )calloc( 1, strlen( string ) + 1 ); |
| if ( ! liter ) { |
| return -1; |
| } |
| |
| /* pass 1: collapse escape sequences, identifying characters which |
| * are to be interpreted literally |
| */ |
| for ( s = string, l = liter ; *s ; s++, l++ ) { |
| if ( *s == '\\' && ! *l ) { |
| fix_escape( s, l ); |
| } |
| } |
| |
| /* pass 2: collapse quoted spans, identifying characters which |
| * are to be interpreted literally |
| */ |
| for ( s = string, l = liter ; *s ; s++, l++ ) { |
| if ( *s == '\"' && ! *l ) { |
| fix_quoted_span( s, l ); |
| } |
| } |
| |
| /* pass 3: collapse white space spans into a single space |
| */ |
| for ( s = string, l = liter ; *s ; s++, l++ ) { |
| if ( is_white( *s ) && ! *l ) { |
| collapse_white( s, l ); |
| } |
| } |
| |
| /* pass 4: identify and null-terminate words |
| */ |
| wordcnt = 0; |
| s = string; |
| l = liter; |
| while ( *s ) { |
| size_t d; |
| if ( wordcnt < ( size_t )slotcnt ) { |
| slotbuf[ wordcnt ] = s; |
| } |
| wordcnt++; |
| d = distance_to_space( s, l ); |
| s += d; |
| l += d; |
| if ( *s ) { |
| *s = 0; |
| s++; |
| l++; |
| } |
| } |
| |
| free( ( void * )liter ); |
| return ( int )wordcnt; |
| } |
| |
| struct escape_table { |
| char sequence; |
| char substitute; |
| }; |
| typedef struct escape_table escape_table_t; |
| |
| static escape_table_t escape_table[ ] = { |
| { 'n', '\n' }, |
| { 't', '\t' }, |
| { 'v', '\v' }, |
| { 'b', '\b' }, |
| { 'r', '\r' }, |
| { 'f', '\f' }, |
| { 'f', '\f' }, |
| { 'a', '\a' }, |
| { '\\', '\\' } |
| }; |
| |
| static void shrink( char *s, size_t cnt ); |
| static int is_hex( char c ); |
| static size_t hex_to_size( char c ); |
| static int is_octal( char c ); |
| static size_t octal_to_size( char c ); |
| |
| static void |
| fix_escape( char *string, char *liter ) |
| { |
| escape_table_t *ep; |
| escape_table_t *endep; |
| |
| /* first look for special escapes described in table |
| */ |
| ep = escape_table; |
| endep = escape_table + ( sizeof( escape_table ) |
| / |
| sizeof( escape_table[ 0 ] )); |
| for ( ; ep < endep ; ep++ ) { |
| if ( string[ 1 ] == ep->sequence ) { |
| string[ 0 ] = ep->substitute; |
| liter[ 0 ] = ( char )1; |
| shrink( &string[ 1 ], 1 ); |
| shrink( &liter[ 1 ], 1 ); |
| return; |
| } |
| } |
| |
| /* detect white space escapes |
| */ |
| if ( is_white( string[ 1 ] )) { |
| liter[ 0 ] = ( char )1; |
| shrink( &string[ 0 ], 1 ); |
| shrink( &liter[ 1 ], 1 ); |
| return; |
| } |
| |
| /* detect hex escapes (don't allow null) |
| */ |
| if ( string[ 1 ] == 'x' ) { |
| size_t hexlen; |
| size_t accum; |
| accum = 0; |
| for ( hexlen = 0 |
| ; |
| hexlen < 2 && is_hex( string[ 2 + hexlen ] ) |
| ; |
| hexlen++ ) { |
| accum *= 16; |
| accum += hex_to_size( string[ 2 + hexlen ] ); |
| } |
| if ( hexlen && accum ) { |
| string[ 0 ] = ( char )accum; |
| liter[ 0 ] = ( char )1; |
| shrink( &string[ 1 ], 1 + hexlen ); |
| shrink( &liter[ 1 ], 1 + hexlen ); |
| return; |
| } |
| } |
| |
| /* detect octal escapes (don't allow null) |
| */ |
| if ( is_octal( string[ 1 ] )) { |
| size_t octallen; |
| size_t accum; |
| accum = octal_to_size( string[ 1 ] ); |
| for ( octallen = 1 |
| ; |
| octallen < 3 && is_octal( string[ 1 + octallen ] ) |
| ; |
| octallen++ ) { |
| accum *= 8; |
| accum += octal_to_size( string[ 1 + octallen ] ); |
| } |
| if ( accum ) { |
| string[ 0 ] = ( char )accum; |
| liter[ 0 ] = ( char )1; |
| shrink( &string[ 1 ], octallen ); |
| shrink( &liter[ 1 ], octallen ); |
| return; |
| } |
| } |
| |
| /* didn't match any escape sequences, so assume literal |
| */ |
| liter[ 0 ] = ( char )1; |
| } |
| |
| static void |
| fix_quoted_span( char *string, char *liter ) |
| { |
| char *s; |
| char *l; |
| |
| /* first cover the leading quote |
| */ |
| shrink( string, 1 ); |
| shrink( liter, 1 ); |
| |
| /* scan for the next non-literal quote, marking all |
| * characters in between as literal |
| */ |
| for ( s = string, l = liter ; *s && ( *s != '\"' || *l ) ; s++, l++ ) { |
| *l = ( char )1; |
| } |
| |
| if ( *s ) { |
| shrink( s, 1 ); |
| shrink( l, 1 ); |
| } |
| } |
| |
| static void |
| collapse_white( char *string, char *liter ) |
| { |
| char *s; |
| char *l; |
| size_t cnt; |
| |
| cnt = 0; |
| for ( s = string, l = liter ; is_white( *s ) && ! *l ; s++, l++ ) { |
| cnt++; |
| } |
| |
| string[ 0 ] = ' '; |
| |
| if ( cnt > 1 ) { |
| shrink( &string[ 1 ], cnt - 1 ); |
| shrink( &liter[ 1 ], cnt - 1 ); |
| } |
| } |
| |
| static size_t |
| distance_to_space( char *s, char *l ) |
| { |
| size_t cnt; |
| |
| for ( cnt = 0 ; *s && ( ! is_white( *s ) || *l ) ; s++, l++ ) { |
| cnt++; |
| } |
| |
| return cnt; |
| } |
| |
| static void |
| shrink( char *s, size_t cnt ) |
| { |
| strcpy( s, s + cnt ); |
| } |
| |
| static int |
| is_white( char c ) |
| { |
| if ( c && strchr( whites, ( int )c )) { |
| return 1; |
| } else { |
| return 0; |
| } |
| } |
| |
| static int |
| is_hex( char c ) |
| { |
| if ( c >= '0' && c <= '9' ) { |
| return 1; |
| } |
| |
| if ( c >= 'a' && c <= 'f' ) { |
| return 1; |
| } |
| |
| if ( c >= 'A' && c <= 'F' ) { |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static size_t |
| hex_to_size( char c ) |
| { |
| if ( c >= '0' && c <= '9' ) { |
| return ( size_t )( c - '0' ); |
| } |
| |
| if ( c >= 'a' && c <= 'f' ) { |
| return ( size_t )( c - 'a' ) + ( size_t )0xa; |
| } |
| |
| if ( c >= 'A' && c <= 'F' ) { |
| return ( size_t )( c - 'A' ) + ( size_t )0xa; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| is_octal( char c ) |
| { |
| if ( c >= '0' && c <= '7' ) { |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static size_t |
| octal_to_size( char c ) |
| { |
| if ( c >= '0' && c <= '7' ) { |
| return ( size_t )( c - '0' ); |
| } |
| |
| return 0; |
| } |
| |
| static int |
| mkdir_r(char *path) |
| { |
| struct stat sbuf; |
| |
| 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; |
| } |