anal.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 1995 Sun Microsystems, Inc. All Rights Reserved
*
* module:
* anal.c
*
* purpose:
* routines to analyze the file trees and figure out what has changed
* and queue files for reconciliation. It also contains tree enumeration
* routines to for other purposes (pruning and link location).
*
* contents:
*
* change analysis:
* analyze .... (top level) analyze all files in the tree for changes
* summary .... print out change/reconciliation statistics for each base
* check_file . (static) look for changes and queue file for reconciliation
* check_changes (static) figure out if a particular file has changed
* queue_file . (static) add a file to the reconciliation list
*
* other tree enumeration functions:
* prune_file . (static) recursive descent and actual pruning
* prune ...... (top level) initiate pruning analysis for nonexistant files
* find_link .. look for other files to which a file may be a link
* link_update. propagate changed stat info to all other links
* same_name .. (static) figure out if two nodes describe same file
*
* misc:
* push_name .. maintain a running full pathname as we descend
* pop_name ... maintain a running full pathname as we pop back
* get_name ... return full pathname for the current file
*
* notes:
* analysis is limited to files that were evaluated in the previous
* pass ... since we don't have complete information about files that
* were not evaluated in the previous pass.
*/
#ident "%W% %E% SMI"
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include "messages.h"
#include "filesync.h"
#include "database.h"
#include "debug.h"
/*
* routines:
*/
void push_name(const char *);
void pop_name();
char *get_name(struct file *);
static errmask_t check_file(struct file *fp);
static diffmask_t check_changes(struct file *fp, int first, int second);
static int prune_file(struct file *fp);
static void queue_file(struct file *fp);
/*
* globals
*/
static struct file *changes; /* list of files to be reconciled */
static long total_files; /* total number of files being considered */
static long est_deletes; /* estimated number of files to be deleted */
static long est_rmdirs; /* est rmdirs of non-empty directories */
int inum_changes; /* LISTed directories whose I#s changed */
/*
* routine:
* analyze
*
* purpose:
* top level routine for the analysis/reconciliation process
*
* parameters:
* none
*
* returns:
* error mask
*
* notes:
* a critical side effect of this routine is the creation of
* the reconciliation list, an ordered list of files that
* needed to be processed in the subsequent reconciliation pass
*/
errmask_t
analyze()
{ struct base *bp;
struct file *fp;
int errs = 0;
int err;
int percentage;
bool_t aborted = FALSE;
char msgbuf[MAX_LINE];
/*
* run through all bases and directories looking for files
* that have been renamed. This must be done before the
* difference analysis because a directory rename can introduce
* radical restructuring into a name-based tree.
*/
for (bp = bases; bp; bp = bp->b_next) {
for (fp = bp->b_files; fp; fp = fp->f_next)
if (fp->f_flags & F_EVALUATE)
errs |= find_renames(fp);
}
/*
* run through all bases and files looking for candidates
* note, however that we only descend into trees that have
* the evaluate flag turned on. As a result of new rules or
* restriction arguments, we may be deliberatly ignoring
* large amounts of the baseline. This means we won't do
* any stats to update the information in those nodes, and
* they will be written back just as they were.
*
* note that there is code to prune out baseline nodes for
* files that no longer exist, but that code is in reconcile
* and will never get a chance to run on nodes that aren't
* analyzed.
*
* we also want to run though all nodes with STAT errors
* so that we can put them on the reconciliation list.
*/
for (bp = bases; bp; bp = bp->b_next) {
for (fp = bp->b_files; fp; fp = fp->f_next)
if (fp->f_flags & (F_EVALUATE|F_STAT_ERROR))
errs |= check_file(fp);
}
/*
* my greatest fear is that someday, somehow, by messing with
* variables or baselines or who-knows-what, that someone will
* run a reconciliation against a large tree that doesn't correspond
* to the baseline, and I will infer that a bazillion files have
* been deleted and will propagate the slaughter before anyone
* can say somebody stop that maniac.
*
* in order to prevent such a possibility, we have a few different
* sanity checks. There is, of course, a tradeoff here between
* danger and irritation. The current set of heuristics for whether
* or not to generate a warning are (any of)
*
* at least CONFIRM_MIN files have been deleted AND
* CONFIRM_PCT of all files have been deleted
*
* the inode number on a LISTed directory has changed
*
* a non-empty directory has been deleted.
*/
msgbuf[0] = 0;
percentage = (est_deletes * 100) / (total_files ? total_files : 1);
if (est_deletes >= CONFIRM_MIN && percentage >= CONFIRM_PCT)
sprintf(msgbuf, gettext(WARN_deletes), est_deletes);
else if (inum_changes > 0)
sprintf(msgbuf, gettext(WARN_ichange), inum_changes);
else if (est_rmdirs)
sprintf(msgbuf, gettext(WARN_rmdirs), est_rmdirs);
if (msgbuf[0])
confirm(msgbuf);
/*
* TRICK:
* the change list contains both files that have changed
* (and probably warrant reconciliation) and files that
* we couldn't get up-to-date stat information on. The
* latter files should just be flagged as being in conflict
* so they can be reported in the summary. The same is
* true of all subsequent files if we abort reconciliation.
*/
for (fp = changes; fp; fp = fp->f_rnext)
if (aborted || (fp->f_flags & F_STAT_ERROR)) {
fp->f_flags |= F_CONFLICT;
/* if it isn't in the baseline yet, don't add it */
if ((fp->f_flags & F_IN_BASELINE) == 0)
fp->f_flags |= F_REMOVE;
fp->f_problem = aborted ? PROB_aborted : PROB_restat;
(fp->f_base)->b_unresolved++;
errs |= ERR_UNRESOLVED;
if (opt_verbose)
fprintf(stdout,
gettext(aborted ? V_suppressed
: V_nostat),
fp->f_fullname);
} else {
err = reconcile(fp);
errs |= err;
if (opt_halt && (err & ERR_ABORT)) {
fprintf(stderr, gettext(ERR_abort_h));
aborted = TRUE;
}
}
return (errs);
}
/*
* routine:
* prune_file
*
* purpose:
* to look for file entries that should be pruned from baseline
* prune the current file if it needs pruning, and recursively
* descend if it is a directory.
*
* parameters:
* pointer to file node
*/
static int
prune_file(struct file *fp)
{ struct file *cp;
int prunes = 0;
/* if node hasn't been evaluated, mark it for removal */
if ((fp->f_flags & (F_EVALUATE|F_STAT_ERROR)) == 0) {
fp->f_flags |= F_REMOVE;
prunes++;
if (opt_debug & DBG_ANAL)
fprintf(stderr, "ANAL: PRUNE %s\n", fp->f_name);
}
/* now check our children */
for (cp = fp->f_files; cp; cp = cp->f_next)
prunes += prune_file(cp);
return (prunes);
}
/*
* routine:
* prune
*
* purpose:
* to prune the baseline of entries that no longer correspond to
* existing rules.
*
* notes:
* This routine just calls prune_file on the top of each base tree.
*/
int
prune()
{ struct base *bp;
struct file *fp;
int prunes = 0;
for (bp = bases; bp; bp = bp->b_next) {
for (fp = bp->b_files; fp; fp = fp->f_next)
prunes += prune_file(fp);
if ((bp->b_flags & F_EVALUATE) == 0)
bp->b_flags |= F_REMOVE;
}
return (prunes);
}
/*
* routine:
* summary
*
* purpose:
* to print out statics and conflict lists
*/
void
summary()
{ struct base *bp;
struct file *fp;
extern bool_t need_super;
(void) fflush(stdout);
for (bp = bases; bp; bp = bp->b_next) {
/* see if this base was irrelevent */
if ((bp->b_flags & F_EVALUATE) == 0)
continue;
/* print out a summary for this base */
fprintf(stderr, gettext(SUM_hd),
bp->b_src_spec, bp->b_dst_spec, bp->b_totfiles);
fprintf(stderr, gettext(SUM_dst),
bp->b_dst_copies, bp->b_dst_deletes, bp->b_dst_misc);
fprintf(stderr, gettext(SUM_src),
bp->b_src_copies, bp->b_src_deletes, bp->b_src_misc);
if (bp->b_unresolved)
fprintf(stderr, gettext(SUM_unresolved),
bp->b_unresolved);
/* print out a list of unreconciled files for this base */
for (fp = changes; fp; fp = fp->f_rnext) {
if (fp->f_base != bp)
continue;
if ((fp->f_flags & F_CONFLICT) == 0)
continue;
fprintf(stderr, "\t\t%s (%s)\n", fp->f_fullname,
fp->f_problem ? fp->f_problem : "???");
}
fprintf(stderr, "\n");
}
if (need_super)
fprintf(stderr, gettext(WARN_super));
}
/*
* routine:
* check_file
*
* purpose:
* figure out if a file requires reconciliation and recursively
* descend into all sub-files and directories
*
* parameters:
* base pointer
* file pointer
*
* returns:
* error mask
* built up changes needed list
* updated statistics
*
* notes:
* this routine builds up a path name as it descends through
* the tree (see push_name, pop_name, get_name).
*/
static errmask_t
check_file(struct file *fp)
{ struct file *cp;
int errs = 0;
if ((fp->f_flags & F_STAT_ERROR) == 0) {
/* see if the source has changed */
fp->f_info[OPT_BASE].f_modtime = fp->f_s_modtime;
fp->f_info[OPT_BASE].f_ino = fp->f_s_inum;
fp->f_info[OPT_BASE].f_d_maj = fp->f_s_maj;
fp->f_info[OPT_BASE].f_d_min = fp->f_s_min;
fp->f_info[OPT_BASE].f_nlink = fp->f_s_nlink;
fp->f_srcdiffs |= check_changes(fp, OPT_BASE, OPT_SRC);
/* see if the destination has changed */
fp->f_info[OPT_BASE].f_modtime = fp->f_d_modtime;
fp->f_info[OPT_BASE].f_ino = fp->f_d_inum;
fp->f_info[OPT_BASE].f_d_maj = fp->f_d_maj;
fp->f_info[OPT_BASE].f_d_min = fp->f_d_min;
fp->f_info[OPT_BASE].f_nlink = fp->f_d_nlink;
fp->f_dstdiffs |= check_changes(fp, OPT_BASE, OPT_DST);
/* if nobody thinks the file exists, baseline needs pruning */
if ((fp->f_flags & (F_IN_SOURCE|F_IN_DEST)) == 0) {
fp->f_srcdiffs |= D_DELETE;
fp->f_dstdiffs |= D_DELETE;
}
/* keep track of possible deletions to look for trouble */
if ((fp->f_dstdiffs | fp->f_srcdiffs) & D_DELETE) {
est_deletes++;
/* see if file is (or has been) a non-empty directory */
if (fp->f_files)
est_rmdirs++;
}
}
/* if we found differences, queue the file for reconciliation */
if (fp->f_srcdiffs || fp->f_dstdiffs || fp->f_flags & F_STAT_ERROR) {
queue_file(fp);
if (opt_debug & DBG_ANAL) {
fprintf(stderr, "ANAL: src=%s",
showflags(diffmap, fp->f_srcdiffs));
fprintf(stderr, " dst=%s",
showflags(diffmap, fp->f_dstdiffs));
fprintf(stderr, " flgs=%s",
showflags(fileflags, fp->f_flags));
fprintf(stderr, " name=%s\n", fp->f_fullname);
}
}
/* bump the total file count */
fp->f_base->b_totfiles++;
total_files++;
/* if this is not a directory, we're done */
if (fp->f_files == 0)
return (errs);
/*
* If this is a directory, we need to recursively analyze
* our children, but only children who have been evaluated.
* If a node has not been evaluated, then we don't have
* updated stat information and there is nothing to analyze.
*
* we also want to run though all nodes with STAT errors
* so that we can put them on the reconciliation list.
* If a directory is unreadable on one side, all files
* under that directory (ON BOTH SIDES) must be marked as
* blocked by stat errors.
*/
push_name(fp->f_name);
for (cp = fp->f_files; cp; cp = cp->f_next) {
if (fp->f_flags & F_STAT_ERROR)
cp->f_flags |= F_STAT_ERROR;
if (cp->f_flags & (F_EVALUATE|F_STAT_ERROR))
errs |= check_file(cp);
}
pop_name();
return (errs);
}
/*
* routine:
* check_changes
*
* purpose:
* to figure out what has changed for a specific file
*
* parameters:
* file pointer
* the reference info
* the info to be checked for changes
*
* returns:
* diff mask
*
* notes:
* this routine doesn't pretend to understand what happened.
* it merely enumerates the ways in which the files differ.
*/
static diffmask_t
check_changes(struct file *fp, int ref, int new)
{ struct fileinfo *rp, *np;
int mask = 0;
int type;
rp = &fp->f_info[ref];
np = &fp->f_info[new];
if (np->f_uid != rp->f_uid)
mask |= D_UID;
if (np->f_gid != rp->f_gid)
mask |= D_GID;
if (np->f_mode != rp->f_mode)
mask |= D_PROT;
type = np->f_type;
if (type != rp->f_type) {
if (type == 0)
mask |= D_DELETE;
else if (rp->f_type == 0)
mask |= D_CREATE;
else
mask |= D_TYPE;
} else if (type == S_IFBLK || type == S_IFCHR) {
/*
* for special files, we only look at the maj/min
*/
if (np->f_rd_maj != rp->f_rd_maj)
mask |= D_SIZE;
if (np->f_rd_min != rp->f_rd_min)
mask |= D_SIZE;
} else if (type != S_IFDIR) {
/*
* for directories, we don't look directly at
* the contents, so these fields don't mean
* anything. If the directories have changed
* in any interesting way, we'll find it by
* walking the tree.
*/
if (np->f_modtime > rp->f_modtime)
mask |= D_MTIME;
if (np->f_size != rp->f_size)
mask |= D_SIZE;
if (np->f_nlink != rp->f_nlink)
mask |= D_LINKS;
}
if (cmp_acls(rp, np) == 0)
mask |= D_FACLS;
return (mask);
}
/*
* routine:
* same_name
*
* purpose:
* to figure out whether or not two databsae nodes actually refer to
* the same file.
*
* parameters:
* pointers to two file description nodes
* which side we should check
*
* returns:
* TRUE/FALSE
*
* notes:
* if a single directory is specified in multiple base pairs, it
* is possible to have multiple nodes in the database describing
* the same file. This routine is supposed to detect those cases.
*
* what should be a trivial string comparison is complicated by
* the possibility that the two nodes might describe the same file
* from base directories at different depths. Thus, rather than
* comparing two strings, we really want to compare the concatenation
* of two pairs of strings. Unfortunately calling full_name would
* be awkward right now, so instead we have our own comparison
* routine that automatically skips from the first string to
* the second.
*/
static bool_t
same_name(struct file *f1, struct file *f2, side_t srcdst)
{
char *s1, *s2, *x1, *x2;
if (srcdst == OPT_SRC) {
s1 = (f1->f_base)->b_src_name;
s2 = (f2->f_base)->b_src_name;
} else {
s1 = (f1->f_base)->b_dst_name;
s2 = (f2->f_base)->b_dst_name;
}
x1 = f1->f_fullname;
x2 = f2->f_fullname;
/*
* Compare the two names, and if they differ before they end
* this is a non-match. If they both end at the same time,
* this is a match.
*
* The trick here is that each string is actually the logical
* concatenation of two strings, and we need to automatically
* wrap from the first to the second string in each pair. There
* is no requirement that the two (concatenated) strings be
* broken at the same point, so we have a slightly baroque
* comparsion loop.
*/
while (*s1 && *s1 == *s2) {
/*
* strings have been identical so far, so advance the
* pointers and continue the comparison. The trick
* is that when either string ends, we have to wrap
* over to its extension.
*/
s1++; s2++;
if (*s1 && *s2)
continue;
/*
* at least one of the strings has ended.
* there is an implicit slash between the string
* and its extension, and this has to be matched
* against the other string.
*/
if (*s1 != *s2) {
if (*s1 == 0 && *s2 == '/')
s2++;
else if (*s2 == 0 && *s1 == '/')
s1++;
else
/* the disagreement doesn't come at a slash */
break;
}
/*
* if either string has ended, wrap to its extension
*/
if (*s1 == 0 && x1 != 0) {
s1 = x1;
x1 = 0;
}
if (*s2 == 0 && x2 != 0) {
s2 = x2;
x2 = 0;
}
}
return (*s1 == *s2);
}
/*
* routine:
* find_link
*
* purpose:
* to figure out if there is a file to which we should
* be creating a link (rather than making a copy)
*
* parameters:
* file node for the file to be created (that we hope is merely a link)
* which side is to be changed (src/dst)
*
* return:
* 0 no link is appropriate
* else pointer to file node for link referent
*
* notes:
* there are a few strange heuristics in this routine and I
* wouldn't bet my soul that I got all of them right. The general
* theory is that when a new file is created, we look to see if it
* is a link to another file on the changed side, and if it is, we
* find the corresponding file on the unchanged side.
*
* cases we want to be able to handle:
* 1. one or more links are created to a prexisting file
* 2. a preexisting only link is renamed
* 3. a rename of one of multiple links to a preexisting file
* 4. a single file is created with multiple links
*/
struct file *
find_link(struct file *fp, side_t srcdst)
{ struct file *lp;
side_t chgside, tgtside;
struct fileinfo *chgp, *tgtp, *basp, *fcp, *ftp;
/* chg = side on which the change was noticed */
/* tgt = side to which the change is to be propagated */
chgside = (srcdst == OPT_SRC) ? OPT_DST : OPT_SRC;
tgtside = (srcdst == OPT_SRC) ? OPT_SRC : OPT_DST;
fcp = &fp->f_info[chgside];
ftp = &fp->f_info[tgtside];
/*
* cases 1 and 3
*
* When a new link is created, we should be able to find
* another file in the changed hierarchy that has the same
* I-node number. We expect it to be on the changed list
* because the link count will have gone up or because all
* of the copies are new. If we find one, then the new file
* on the receiving file should be a link to the corresponding
* existing file.
*
* case 4
*
* the first link will be dealt with as a copy, but all
* subsequent links should find an existing file analogous
* to one of the links on the changed side, and create
* corresponding links on the other side.
*
* in each of these cases, there should be multiple links
* on the changed side. If the linkcount on the changed
* side is one, we needn't bother searching for other links.
*/
if (fcp->f_nlink > 1)
for (lp = changes; lp; lp = lp->f_rnext) {
/* finding the same node doesn't count */
if (fp == lp)
continue;
tgtp = &lp->f_info[tgtside];
chgp = &lp->f_info[chgside];
/*
* if the file doesn't already exist on the target side
* we cannot make a link to it
*/
if (tgtp->f_mode == 0)
continue;
/*
* if this is indeed a link, then the prospective file on
* the changed side will have the same dev/inum as the file
* we are looking for
*/
if (fcp->f_d_maj != chgp->f_d_maj)
continue;
if (fcp->f_d_min != chgp->f_d_min)
continue;
if (fcp->f_ino != chgp->f_ino)
continue;
/*
* if the target side is already a link to this file,
* then there is no new link to be created
* FIX: how does this interact with copies over links
*/
if ((ftp->f_d_maj == tgtp->f_d_maj) &&
(ftp->f_d_min == tgtp->f_d_min) &&
(ftp->f_ino == tgtp->f_ino))
continue;
/*
* there is a pathological situation where a single file
* might appear under multiple base directories. This is
* damned awkward to detect in any other way, so we must
* check to see if we have just found another database
* instance for the same file (on the changed side).
*/
if ((fp->f_base != lp->f_base) && same_name(fp, lp, chgside))
continue;
if (opt_debug & DBG_ANAL)
fprintf(stderr, "ANAL: FIND LINK %s and %s\n",
fp->f_fullname, lp->f_fullname);
return (lp);
}
/*
* case 2: a simple rename of the only link
*
* In this case, there may not be any other existing file on
* the changed side that has the same I-node number. There
* might, however, be a record of such a file in the baseline.
* If we can find an identical file with a different name that
* has recently disappeared, we have a likely rename.
*/
for (lp = changes; lp; lp = lp->f_rnext) {
/* finding the same node doesn't count */
if (fp == lp)
continue;
tgtp = &lp->f_info[tgtside];
chgp = &lp->f_info[chgside];
/*
* if the file still exists on the changed side this is
* not a simple rename, and in fact the previous pass
* would have found it.
*/
if (chgp->f_mode != 0)
continue;
/*
* the inode number for the new link on the changed
* side must match the inode number for the old link
* from the baseline.
*/
if (fcp->f_d_maj != ((srcdst == OPT_SRC) ? lp->f_d_maj
: lp->f_s_maj))
continue;
if (fcp->f_d_min != ((srcdst == OPT_SRC) ? lp->f_d_min
: lp->f_s_min))
continue;
if (fcp->f_ino != ((srcdst == OPT_SRC) ? lp->f_d_inum
: lp->f_s_inum))
continue;
/* finding a file we are already linked to doesn't help */
if ((ftp->f_d_maj == tgtp->f_d_maj) &&
(ftp->f_d_min == tgtp->f_d_min) &&
(ftp->f_ino == tgtp->f_ino))
continue;
/*
* there is a danger that we will confuse an
* inode reallocation with a rename. We should
* only consider this to be a rename if the
* new file is identical to the old one
*/
basp = &lp->f_info[OPT_BASE];
if (fcp->f_type != basp->f_type)
continue;
if (fcp->f_size != basp->f_size)
continue;
if (fcp->f_mode != basp->f_mode)
continue;
if (fcp->f_uid != basp->f_uid)
continue;
if (fcp->f_gid != basp->f_gid)
continue;
if (opt_debug & DBG_ANAL)
fprintf(stderr, "ANAL: FIND RENAME %s and %s\n",
fp->f_fullname, lp->f_fullname);
return (lp);
}
return (0);
}
/*
* routine:
* has_other_links
*
* purpose:
* to determine whether or not there is more that one link to a
* particular file. We are willing to delete a link to a file
* that has changed if we will still have other links to it.
* The trick here is that we only care about links under our
* dominion.
*
* parameters:
* file pointer to node we are interested in
* which side we are looking to additional links on
*
* returns:
* TRUE if there are multiple links
* FALSE if this is the only one we know of
*/
bool_t
has_other_links(struct file *fp, side_t srcdst)
{ struct file *lp;
struct fileinfo *fip, *lip;
fip = &fp->f_info[srcdst];
/* if the link count is one, there couldn't be others */
if (fip->f_nlink < 2)
return (FALSE);
/* look for any other files for the same inode */
for (lp = changes; lp; lp = lp->f_rnext) {
/* finding the same node doesn't count */
if (fp == lp)
continue;
lip = &lp->f_info[srcdst];
/*
* file must still exist on this side
*/
if (lip->f_mode == 0)
continue;
/*
* if this is indeed a link, then the prospective file on
* the changed side will have the same dev/inum as the file
* we are looking for
*/
if (lip->f_d_maj != fip->f_d_maj)
continue;
if (lip->f_d_min != fip->f_d_min)
continue;
if (lip->f_ino != fip->f_ino)
continue;
/*
* we have found at least one other link
*/
return (TRUE);
}
return (FALSE);
}
/*
* routine:
* link_update
*
* purpose:
* to propoagate a stat change to all other file nodes that
* correspond to the same I-node on the changed side
*
* parameters:
* file pointer for the updated file
* which side was changed
*
* returns:
* void
*
* notes:
* if we have copied onto a file, we have copied onto all
* of its links, but since we do all stats before we do any
* copies, the stat information recently collected for links
* is no longer up-to-date, and this would result in incorrect
* reconciliation (redundant copies).
*
* There is an assumption here that all links to a changed
* file will be in the change list. This is true for almost
* all cases not involving restriction. If we do fail to
* update the baseline for a file that was off the change list,
* the worst that is likely to happen is that we will think
* it changed later (but will almost surely find that both
* copies agree).
*/
void
link_update(struct file *fp, side_t which)
{ struct file *lp;
for (lp = changes; lp; lp = lp->f_rnext) {
/* finding the current entry doesn't count */
if (lp == fp)
continue;
/* look for same i#, maj, min on changed side */
if (lp->f_info[which].f_ino != fp->f_info[which].f_ino)
continue;
if (lp->f_info[which].f_d_maj != fp->f_info[which].f_d_maj)
continue;
if (lp->f_info[which].f_d_min != fp->f_info[which].f_d_min)
continue;
/*
* this appears to be another link to the same file
* so the updated stat information for one must be
* correct for the other.
*/
lp->f_info[which].f_type = fp->f_info[which].f_type;
lp->f_info[which].f_size = fp->f_info[which].f_size;
lp->f_info[which].f_mode = fp->f_info[which].f_mode;
lp->f_info[which].f_uid = fp->f_info[which].f_uid;
lp->f_info[which].f_gid = fp->f_info[which].f_gid;
lp->f_info[which].f_modtime = fp->f_info[which].f_modtime;
lp->f_info[which].f_modns = fp->f_info[which].f_modns;
lp->f_info[which].f_nlink = fp->f_info[which].f_nlink;
lp->f_info[which].f_rd_maj = fp->f_info[which].f_rd_maj;
lp->f_info[which].f_rd_min = fp->f_info[which].f_rd_min;
if (opt_debug & DBG_STAT)
fprintf(stderr,
"STAT: UPDATE LINK, file=%s, mod=%08lx.%08lx\n",
lp->f_name, lp->f_info[which].f_modtime,
lp->f_info[which].f_modns);
}
}
/*
* routine:
* queue_file
*
* purpose:
* append a file to the list of needed reconciliations
*
* parameters:
* pointer to file
*
* notes:
* when a request is appended to the reconciliation list,
* we fill in the full name. We delayed this in hopes that
* it wouldn't be necessary (saving cycles and memory)
*
* There is some funny business with modification times.
* In general, we queue files in order of the latest modification
* time so that propagations preserve relative ordering. There
* are, however, a few important exceptions:
* 1. all directory creations happen at time zero,
* so that they are created before any files can
* be added to them.
* 2. all directory deletions happen at time infinity-depth,
* so that everything else can be removed before the
* directories themselves are removed.
* 3. all file deletions happen at time infinity-depth
* so that (in renames) the links will preceed the unlinks.
*/
static void
queue_file(struct file *fp)
{ struct file **pp, *np;
#define TIME_ZERO 0L /* the earliest possible time */
#define TIME_LONG 0x7FFFFFFF /* the latest possible time */
/*
* figure out the modification time for sequencing purposes
*/
if ((fp->f_srcdiffs|fp->f_dstdiffs) & D_DELETE) {
/*
* deletions are performed last, and depth first
*/
fp->f_modtime = TIME_LONG - fp->f_depth;
} else if (fp->f_info[OPT_SRC].f_type != S_IFDIR &&
fp->f_info[OPT_DST].f_type != S_IFDIR) {
/*
* for most files we use the latest mod time
*/
fp->f_modtime = fp->f_info[OPT_SRC].f_modtime;
fp->f_modns = fp->f_info[OPT_SRC].f_modns;
if (fp->f_modtime < fp->f_info[OPT_DST].f_modtime) {
fp->f_modtime = fp->f_info[OPT_DST].f_modtime;
fp->f_modns = fp->f_info[OPT_DST].f_modns;
}
} else {
/*
* new directory creations need to happen before anything
* else and are automatically sequenced in traversal order
*/
fp->f_modtime = TIME_ZERO;
}
/*
* insertion is time ordered, and for equal times,
* insertions is in (pre-order) traversal order
*/
for (pp = &changes; (np = *pp) != 0; pp = &np->f_rnext) {
if (fp->f_modtime > np->f_modtime)
continue;
if (fp->f_modtime < np->f_modtime)
break;
if (fp->f_modns < np->f_modns)
break;
}
fp->f_fullname = strdup(get_name(fp));
fp->f_rnext = np;
*pp = fp;
}
/*
* routines:
* push_name/pop_name/get_name
*
* purpose:
* maintain a name stack so we can form name of a particular file
* as the concatenation of all of the names between it and the
* (know to be fully qualified) base directory.
*
* notes:
* we go to this trouble because most files never change and
* so we don't need to associate full names with every one.
* This stack is maintained during analysis, and if we decide
* to add a file to the reconciliation list, we can use the
* stack to generate a fully qualified name at that time.
*
* we compress out '/./' when we return a name. Given that the
* stack was built by a tree walk, the only place a /./ should
* appear is at the first level after the base ... but there
* are legitimate ways for them to appear there.
*
* these names can get deep, so we dynamically size our name buffer
*/
static const char *namestack[ MAX_DEPTH + 1 ];
static int namedepth = 0;
static int namelen = 0;
void
push_name(const char *name)
{
namestack[ namedepth++ ] = name;
namelen += 2 + strlen(name);
/* make sure we don't overflow our name stack */
if (namedepth >= MAX_DEPTH) {
fprintf(stderr, gettext(ERR_deep), name);
exit(ERR_OTHER);
}
}
void
pop_name(void)
{
namelen -= 2 + strlen(namestack[--namedepth]);
namestack[ namedepth ] = 0;
#ifdef DBG_ERRORS
/* just a little sanity check here */
if (namedepth <= 0) {
if (namedepth < 0) {
fprintf(stderr, "ASSERTION FAILURE: namedepth < 0\n");
exit(ERR_OTHER);
} else if (namelen != 0) {
fprintf(stderr, "ASSERTION FAILURE: namelen != 0\n");
exit(ERR_OTHER);
}
}
#endif
}
char
*get_name(struct file *fp)
{ int i;
static char *namebuf = 0;
static int buflen = 0;
/* make sure we have an adequate buffer */
i = namelen + 1 + strlen(fp->f_name);
if (buflen < i) {
for (buflen = MAX_PATH; buflen < i; buflen += MAX_NAME);
namebuf = (char *) realloc(namebuf, buflen);
}
/* assemble the name */
namebuf[0] = 0;
for (i = 0; i < namedepth; i++) {
if (strcmp(namestack[i], ".")) {
strcat(namebuf, namestack[i]);
strcat(namebuf, "/");
}
}
strcat(namebuf, fp->f_name);
return (namebuf);
}