recon.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
* 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:
* recon.c
*
* purpose:
* process the reconciliation list, figure out exactly what the
* changes were, and what we should do about them.
*
* contents:
* reconcile ... (top level) process the reconciliation list
* samedata .... (static) do two files have the same contents
* samestuff ... (static) do two files have the same ownership/protection
* samecompare . (static) actually read and compare the contents
* samelink .... (static) do two symlinks have the same contents
* truncated ... (static) was one of the two copies truncted
* older ....... (static) which copy is older
* newer ....... (static) which copy is newer
* full_name ... generate a full path name for a file
*
* notes:
* If you only study one routine in this whole program, reconcile
* is that routine. Everything else is just book keeping.
*
* things were put onto the reconciliation list because analyze
* thought that they might have changed ... but up until now
* nobody has figured out what the changes really were, or even
* if there really were any changes.
*
* queue_file has ordered the reconciliation list with directory
* creations first (depth ordered) and deletions last (inversely
* depth ordered). all other changes have been ordered by mod time.
*/
#ident "%W% %E% SMI"
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include "filesync.h"
#include "database.h"
#include "messages.h"
#include "debug.h"
/*
* local routines to figure out how the files really differ
*/
/*
* globals
*/
char *srcname; /* file we are emulating */
char *dstname; /* file we are updating */
/*
* routine:
* reconcile
*
* purpose:
* to perform the reconciliation action associated with a file
*
* parameters:
* file pointer
*
* returns:
* built up error mask
* updated statistics
*
* notes:
* The switch statement handles the obvious stuff.
* The TRUE side of the samedata test handles minor differences.
* The interesting stuff is in the FALSE side of the samedata test.
*
* The desparation heuristics (in the diffmask&CONTENTS test) are
* not rigorously correct ... but they always try do the right thing
* pathological cases. But I claim that the benefits outweigh the
* risks, and most users will be pleased with the resulting decisions.
*
* Another trick is in the deletion cases of the switch. We
* normally won't allow an unlink that conflicts with data
* changes. If there are multiple links to the file, however,
* we can make the changes and do the deletion.
*
* The action routines do_{remove,rename,like,copy} handle all
* of their own statistics and status updating. This routine
* only has to handle its own reconciliation failures (when we
* can't decide what to do).
*/
fp->f_fullname,
/*
* form the fully qualified names for both files
*/
/*
* because they are so expensive to read and so troublesome
* to set, we try to put off reading ACLs as long as possible.
* If we haven't read them yet, we must read them now (so that
* samestuff can compare them).
*/
}
/*
* If a rename has been detected, we don't have to figure
* it out, since both the rename-to and rename-from files
* have already been designated. When we encounter a rename-to
* we should carry it out. When we encounter a rename-from
* we can ignore it, since it should be dealt with as a side
* effect of processing the rename-to.
*/
return (0);
if (opt_verbose)
}
if (errs != ERR_RESOLVABLE)
goto done;
/*
* if any differences remain, then we may be dealing
* with contents changes in addition to a rename
*/
goto done;
/*
* fall through to reconcile the data changes
*/
}
/*
* pull of the easy cases (non-conflict creations & deletions)
*/
case F_IN_BASELINE: /* only exists in baseline */
case 0: /* only exists in rules */
if (opt_verbose)
fp->f_fullname);
return (0);
/*
* the basic principle here is that we are willing
* to do the deletion if:
* no changes were made on the other side
* OR
* we have been told to force in this direction
*
* we do, however, make an exception for files that
* will still have other links. In this case, the
* (changed) data will still be accessable through
* another link and so we are willing to do the unlink
* inspite of conflicting changes (which may well
* have been introduced through another link.
*
* The jury is still out on this one
*/
if (opt_verbose)
goto done;
}
/* a deletion combined with changes */
if (opt_verbose)
fp->f_fullname);
/* if we are to resolve in favor of source */
goto done;
}
goto cant;
if (opt_verbose)
goto done;
}
/* a deletion combined with changes */
if (opt_verbose)
fp->f_fullname);
/* if we are to resolve in favor of destination */
goto done;
}
goto cant;
/*
* if something new shows up, and for some reason we cannot
* propagate it to the other side, we should suppress the
* file from the baseline, so it will show up as a new
* creation next time too.
*/
case F_IN_SOURCE: /* created in src */
if (opt_verbose)
goto done;
case F_IN_DEST: /* created in dest */
if (opt_verbose)
goto done;
/*
* since we don't have a baseline, we cannot
* know which of the two copies should prevail
*/
break;
/*
* we have a baseline where the two copies agreed,
* so maybe we can determine that only one of the
* two copies have changed ... but before we decide
* who should be the winner we should determine
* that the two copies are actually different.
*/
break;
}
/*
* if we have fallen out of the case statement, it is because
* we have discovered a non-obvious situation where potentially
* changed versions of the file exist on both sides.
*
* if the two copies turn out to be identical, this is simple
*/
/* files are identical, just update baseline */
if (opt_verbose)
fp->f_fullname);
goto done;
} else {
/*
* contents agree but ownership/protection does
* not agree, so we have to bring these into
* agreement. We can pick a winner if one
* side hasn't changed, or if the user has
* specified a force flag.
*/
if (opt_verbose)
fp->f_fullname);
goto done;
}
goto done;
}
}
/* falls down to cant */
} else {
/*
* The two files have different contents, so we have
* a potential conflict here. If we know that only one
* side has changed, we go with that side.
*/
if (opt_verbose)
fp->f_fullname);
goto done;
}
/*
* Both sides have changed, so we have a real conflict.
*/
if (opt_verbose)
fp->f_fullname);
/*
* See if the user has given us explicit instructions
* on how to resolve conflicts. We may have been told
* to favor the older, the newer, the source, or the
* destination ... but the default is to leave the
* conflict unresolved.
*/
goto done;
}
goto done;
}
if (opt_force != 0) {
goto done;
}
/*
* This is our last chance before giving up.
*
* We know that the files have different contents and
* that there were changes on both sides. The only way
* we can safely handle this is if there were pure contents
* changes on one side and pure ownership changes on the
* other side. In this case we can propagate the ownership
* one way and the contents the other way.
*
* We decide whether or not this is possible by ANDing
* together the changes on the two sides, and seeing
* if the changes were all orthogonal (none of the same
* things changed on both sides).
*/
if ((diffmask & D_CONTENTS) == 0) {
/*
* if ownership changes were only made on one side
* (presumably the side that didn't have data changes)
* we can handle them separately. In this case,
* ownership changes must be fixed first, because
* the subsequent do_copy will overwrite them.
*/
TRUE);
/*
* Now we can deal with the propagation of the data
* changes. Note that any ownership/protection
* changes (from the other side) that have not been
* propagated yet are about to be lost. The cases
* in which this might happen are all pathological
* and the consequences of losing the protection
* changes are (IMHO) minor when compared to the
* obviously correct data propagation.
*/
goto done;
}
/*
* there are conflicting changes, nobody has told us how to
* resolve conflicts, and we cannot figure out how to merge
* the differences.
*/
}
cant:
/*
* I'm not smart enough to resolve this conflict automatically,
* so I have no choice but to bounce it back to the user.
*/
errs |= ERR_UNRESOLVED;
done:
/*
* if we have a conflict and the file is not in the baseline,
* then there was never any point at which the two copies were
* in agreement, and we want to preserve the conflict for future
* resolution.
*/
/*
* in most cases, this is most easily done by just
* excluding the file in question from the baseline
*/
else
/*
* but ... if the file in question is a directory
* with children, excluding it from the baseline
* would keep all of its children (even those with
* no conflicts) out of the baseline as well. In
* This case, it is better to tell a lie and to
* manufacture a point of imaginary agreement
* in the baseline ... but one that is absurd enough
* that we will still see conflicts each time we run.
*
* recording a type of directory, and everything
* else as zero should be absurd enough.
*/
return (errs);
}
/*
* routine:
* newer
*
* purpose:
* determine which of two files is newer
*
* parameters:
* struct file
*
* returns:
*/
static side_t
{
return (OPT_SRC);
return (OPT_DST);
return (OPT_SRC);
return (OPT_DST);
}
/*
* routine:
* older
*
* purpose:
* determine which of two files is older
*
* parameters:
* struct file
*
* returns:
*/
static side_t
{
return (OPT_SRC);
return (OPT_DST);
return (OPT_SRC);
return (OPT_DST);
}
/*
* routine:
* samedata
*
* purpose:
* determine whether or not two files contain the same data
*
* parameters:
* struct file
*
* returns:
*/
static bool_t
{
/* cheap test: types are different */
return (FALSE);
/* cheap test: directories have same contents */
return (TRUE);
return (FALSE);
return (FALSE);
return (TRUE);
}
/* symlinks are the same if their contents are the same */
return (samelink());
/* cheap test: sizes are different */
return (FALSE);
/* expensive test: byte for byte comparison */
if (samecompare(fp) == 0)
return (FALSE);
return (TRUE);
}
/*
* routine:
* samestuff
*
* purpose:
* determine whether or not two files have same owner/protection
*
* parameters:
* struct file
*
* returns:
*/
static bool_t
/* if the are all the same, it is easy to tell the truth */
return (TRUE);
/* note the nature of the conflict */
else
return (FALSE);
}
/*
* routine:
* samecompare
*
* purpose:
* do a byte-for-byte comparison of two files
*
* parameters:
* struct file
*
* returns:
*/
static bool_t
int i, count;
if (sfd < 0)
return (FALSE);
if (dfd < 0) {
return (FALSE);
}
for (
count > 0;
/* do a matching read */
goto done;
}
/* do the comparison for this block */
for (i = 0; i < count; i++) {
goto done;
}
}
}
done:
return (same);
}
/*
* routine:
* truncated
*
* purpose:
* to determine whether or not a file has been truncated
*
* parameters:
* pointer to file structure
*
* returns:
*/
static bool_t
{
/* either source or destination must now be zero length */
return (FALSE);
/* file must have originally had a non-zero length */
return (FALSE);
/* file type must "normal" all around */
return (FALSE);
return (FALSE);
return (FALSE);
return (TRUE);
}
/*
* routine:
* samelink
*
* purpose:
* to determine whether or not two symbolic links agree
*
* parameters:
* pointer to file structure
*
* returns:
*/
static bool_t
samelink()
/* read both copies of the link */
/* if they aren't the same length, they disagree */
return (FALSE);
/* look for differences in contents */
for (i = 0; i < srclen; i++)
return (FALSE);
return (TRUE);
}
/*
* routine:
* full_name
*
* purpose:
* to figure out the fully qualified path name to a file on the
* reconciliation list.
*
* parameters:
* pointer to the file structure
* side indication for which base to use
* side indication for which buffer to use
*
* returns:
* pointer to a clobberable buffer
*
* notes:
* the zero'th buffer is used for renames and links, where
* we need the name of another file on the same side.
*/
char *
{ static char *buffers[3];
static int buflen = 0;
char *p, *b;
int l;
/* see if the existing buffer is long enough */
/* see if the allocated buffer is long enough */
if (l > buflen) {
/* figure out the next "nice" size to use */
/* reallocate all buffers to this size */
for (l = 0; l < 3; l++) {
if (buffers[l] == 0)
nomem("full name");
}
}
/* assemble the name in the buffer and reurn it */
strcpy(p, b);
strcat(p, "/");
return (p);
}