blob: 3f3084e3aa627aaac9ce6791ad50a3854339582c [file] [log] [blame]
/*
* 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;
}