/*
* 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:
* action.c
*
* purpose:
* routines to carryout reconciliation actions and make the
* appropriate updates to the database file structure.
*
* contents:
* do_like ... change ownership and protection
* do_copy ... copy a file from one side to the other
* do_remove . remove a file from one side
* do_rename . rename a file on one side
* copy ...... (static) do the actual copy
* checksparse (static) figure out if a file is sparse
*
* ASSERTIONS:
* any of these action routines is responsible for all baseline
* and statistics updates associated with the reconciliation
* actions. If notouch is specified, they should fake the
* updates well enough so that link tests will still work.
*
* success:
* bump bp->b_{src,dst}_{copies,deletes,misc}
* update fp->f_info[srcdst]
* update fp->f_info[OPT_BASE] from fp->f_info[srcdst]
* if there might be multiple links, call link_update
* return ERR_RESOLVABLE
*
* failure:
* set fp->f_flags |= F_CONFLICT
* set fp->f_problem
* bump bp->b_unresolved
* return ERR_UNRESOLVED
*
* pretend this never happened:
* return 0, and baseline will be unchanged
*
* notes:
* Action routines can be called in virtually any order
* or combination, and it is certainly possible for an
* earlier action to succeed while a later action fails.
* If each successful action results in a completed baseline
* update, a subsequent failure will force the baseline to
* roll back to the last success ... which is appropriate.
*/
#ident "%W% %E% SMI"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <utime.h>
#include <errno.h>
#include "filesync.h"
#include "database.h"
#include "messages.h"
#include "debug.h"
/*
* globals and importeds
*/
extern char *srcname; /* file we are emulating */
extern char *dstname; /* file we are updating */
/*
* locals
*/
static int checksparse(int);
/*
* routine:
* do_like
*
* purpose:
* to propagate ownership and protection changes between
* one existing file and another.
*
* parameters:
* file pointer
* whether or not to update statistics (there may be a copy and a like)
*
* returns:
* error mask
*
* notes:
* if we are called from reconcile, we should update
* the statistics, but if we were called from do_copy
* that routine will do the honors.
*/
{ char *dst;
int rc = 0;
char *errstr = 0;
extern int errno;
/* see if this is a forbidden propagation */
if (srcdst == opt_oneway) {
bp->b_unresolved++;
return (ERR_UNRESOLVED);
}
/* get info about source and target files */
} else {
}
/* figure out what needs fixing */
/*
* try to anticipate things that we might not be able to
* do, and return appropriate errorst if the calling user
* cannot safely perform the requiested updates.
*/
if (my_uid != 0) {
if (do_chown)
if (do_chmod)
else if (do_acls)
else if (do_chgrp)
}
#ifdef ACL_UID_BUG
#endif
if (errstr) {
need_super = TRUE;
/* if the user doesn't care, shine it on */
if (opt_everything == 0)
return (0);
/* if the user does care, return the error */
rc = -1;
goto nogood;
}
}
if (do_chmod)
if (do_acls)
if (do_chown)
if (do_chgrp)
}
if (do_chmod) {
if (!opt_quiet)
#ifdef DBG_ERRORS
/* should we simulate a chmod failure */
rc = -1;
else
#endif
/* update dest and baseline to reflect the change */
if (rc == 0) {
} else
}
/*
* see if we need to fix the acls
*/
if (!opt_quiet)
#ifdef DBG_ERRORS
/* should we simulate a set acl failure */
rc = -1;
else
#endif
/* update dest and baseline to reflect the change */
if (rc == 0) {
#ifdef ACL_UID_BUG
do_chown = 1;
}
do_chgrp = 1;
}
#endif
/*
* if the file system doesn't support ACLs
* we should just pretend we never saw them
*/
rc = 0;
} else
}
/*
* see if we need to fix the ownership
*/
if (do_chown)
if (do_chgrp)
#ifdef DBG_ERRORS
/* should we simulate a chown failure */
rc = -1;
else
#endif
/* update the destination to reflect changes */
if (rc == 0) {
} else {
need_super = TRUE;
if (opt_everything == 0)
return (0);
}
if (rc != 0)
PROB_chown : PROB_chgrp);
}
}
/*
* if we were successful, we should make sure the other links
* see the changes. If we were called from do_copy, we don't
* want to do the link_updates either because do_copy will
* handle them too.
*/
if (!do_stats)
return (errs);
if (rc != 0) {
bp->b_unresolved++;
} else {
/*
* it worked, so update the baseline and statistics
*/
bp->b_src_misc++;
else
bp->b_dst_misc++;
errs |= ERR_RESOLVABLE;
}
return (errs);
}
/*
* routine:
* do_copy
*
* purpose:
* to propagate a creation or change
*
* parameters:
* file pointer
*
* returns:
* error mask
*
* note:
* structure for the updated file. This is somewhat redundant
* because we will restat at the end of the routine, but these
* anticipatory updates help to ensure that the link finding
* code will still behave properly in notouch mode (when restats
* cannot be done).
*/
int rc;
long mtime;
int do_chmod = 0;
int do_chown = 0;
int do_chgrp = 0;
int do_unlink = 0;
int do_acls = 0;
int do_create = 0;
/* see if this is a forbidden propagation */
if (srcdst == opt_oneway) {
bp->b_unresolved++;
return (ERR_UNRESOLVED);
}
/* figure out who is the source and who is the destination */
} else {
}
/* note information about the file to be created */
/*
* creating a file does not guarantee it will get the desired
* modes, uid and gid. If the file already exists, it will
* are not the desired ones, the new file will also require
* manual correction. If the file has the wrong type, we will
* need to delete it and recreate it. If the file is not writable,
* it is easier to delete it than to chmod it to permit overwrite
*/
/* if the file already exists */
do_chown = 1;
do_chgrp = 1;
do_chmod = 1;
} else {
/* if we will be creating a new file */
do_create = 1;
do_unlink = 1;
do_chown = 1;
do_chgrp = 1;
}
/*
* if the source has acls, we will surely have to set them for dest
*/
do_acls = 1;
/*
* for any case other than replacing a normal file with a normal
* file, we need to delete the existing file before creating
* the new one.
*/
if (do_unlink) {
if (!opt_quiet)
#ifdef DBG_ERRORS
/* should we simulate a rmdir failure */
rc = -1;
else
#endif
} else {
if (!opt_quiet)
#ifdef DBG_ERRORS
/* should we simulate a unlink failure */
rc = -1;
else
#endif
}
if (rc != 0)
goto cant;
/* note that this file no longer exists */
}
if (do_unlink)
if (do_chmod)
if (do_acls)
if (do_chown)
if (do_chgrp)
}
/*
* how we go about copying a file depends on what type of file
* it is that we are supposed to copy
*/
switch (type) {
case S_IFDIR:
if (!opt_quiet) {
}
#ifdef DBG_ERRORS
/* should we simulate a mkdir failure */
rc = -1;
else
#endif
/* update stat with what we have just created */
if (rc == 0) {
}
break;
case S_IFLNK:
#ifdef DBG_ERRORS
/* should we simulate a symlink read failure */
rc = -1;
else
#endif
if (rc > 0) {
if (!opt_quiet) {
}
#ifdef DBG_ERRORS
/* should we simulate a symlink failure */
rc = -1;
else
#endif
if (rc == 0)
}
break;
case S_IFBLK:
case S_IFCHR:
if (!opt_quiet)
#ifdef DBG_ERRORS
/* should we simulate a mknod failure */
rc = -1;
else
#endif
rc = opt_notouch ? 0
/* update stat with what we have just created */
if (rc == 0) {
do_chmod = 1;
}
break;
case S_IFREG:
/*
* The first thing to do is ascertain whether or not
* the alleged new copy might in fact be a new link.
* We trust find_link to weigh all the various factors,
* so if he says make a link, we'll do it.
*/
if (lp) {
/* figure out name of existing file */
/*
* if file already exists, it must be deleted
*/
if (!opt_quiet)
#ifdef DBG_ERRORS
/* should we simulate a unlink failure */
rc = -1;
else
#endif
/*
* if we couldn't do the unlink, we must
* mark the linkee in conflict as well
* so his reference count remains the same
* in the baseline and he continues to show
* up on the change list.
*/
if (rc != 0) {
goto cant;
}
}
if (!opt_quiet) {
}
#ifdef DBG_ERRORS
/* should we simulate a link failure */
rc = -1;
else
#endif
/*
* if this is a link, there is no reason to worry
* about ownership and modes, they are automatic
*/
if (rc == 0) {
break;
} else {
/*
* if we failed to make a link, we want to
* mark the linkee in conflict too, so that
* his reference count remains the same in
* the baseline, and he shows up on the change
* list again next time.
*/
break;
}
/*
* in some situation we haven't figured out yet
* we might want to fall through and try a copy
* if the link failed.
*/
}
/* we are going to resolve this by making a copy */
if (!opt_quiet) {
}
if (rc != 0) {
if (copy_err_str)
else
/*
* The new copy (if it exists at all) is a botch.
* If this was a new create or a remove and copy
* we should get rid of the botched copy so that
* it doesn't show up as two versions next time.
*/
if (do_create)
/* FIX: inode number is still wrong */
}
/* for normal files we have an option to preserve mod time */
/* ignore the error return on this one */
}
break;
default:
rc = -1;
}
/*
* if any of the file's attributes need attention, I should let
* do_like take care of them, since it knows all rules for who
* can and cannot make what types of changes.
*/
}
/*
* finish off by re-stating the destination and using that to
* update the baseline. If we were completely successful in
* If we were unable to make all the necessary changes, stating
* the destination will make the source appear to have changed,
* so that the differences will continue to reappear as new
* changes (inconsistancies).
*/
if (rc == 0)
if (!opt_notouch) {
#ifdef DBG_ERRORS
/* should we simulate a restat failure */
rc = -1;
else
#endif
if (rc == 0) {
if (do_acls)
}
} else {
/*
* BOGOSITY ALERT
* we are in notouch mode and haven't really
* done anything, but if we want link detection
* to work and be properly reflected in the
* what-I-would-do output for a case where
* multiple links are created to a new file,
* we have to make the new file appear to
* have been created. Since we didn't create
* the new file we can't stat it, but if
* no file exists, we can't make a link to
* it, so we will pretend we created a file.
*/
}
}
bp->b_unresolved++;
if (errs == 0)
errs |= ERR_UNRESOLVED;
} else {
/* update the statistics */
bp->b_src_copies++;
else
bp->b_dst_copies++;
errs |= ERR_RESOLVABLE;
}
return (errs);
}
/*
* routine:
* do_remove
*
* purpose:
* to propagate a deletion
*
* parameters:
* file pointer
*
* returns:
* error mask
*/
{ char *name;
int rc;
/* see if this is a forbidden propagation */
if (srcdst == opt_oneway) {
bp->b_unresolved++;
return (ERR_UNRESOLVED);
}
if (!opt_quiet)
#ifdef DBG_ERRORS
/* should we simulate a rmdir failure */
rc = -1;
else
#endif
} else {
if (!opt_quiet)
#ifdef DBG_ERRORS
/* should we simulate an unlink failure */
rc = -1;
else
#endif
}
if (rc == 0) {
/* tell any other hard links that one has gone away */
else
errs |= ERR_RESOLVABLE;
} else {
bp->b_unresolved++;
}
return (errs);
}
/*
* routine:
* do_rename
*
* purpose:
* to propagate a rename
*
* parameters:
* file pointer for the new name
*
* returns:
* error mask
*/
{ int rc;
char *newname;
char *oldname;
/* see if this is a forbidden propagation */
if (srcdst == opt_oneway) {
/* if we can't resolve the TO, the FROM is also unresolved */
bp->b_unresolved++;
return (ERR_UNRESOLVED);
}
if (!opt_quiet)
#ifdef DBG_ERRORS
/* should we simulate a rename failure */
rc = -1;
else
#endif
/* if we succeed, update the baseline */
if (rc == 0)
if (!opt_notouch) {
#ifdef DBG_ERRORS
/* should we simulate a restat failure */
rc = -1;
else
#endif
if (rc == 0) {
}
} else {
/*
* BOGOSITY ALERT
* in order for link tests to work in notouch
* mode we have to dummy up some updated status
*/
}
else
if (rc == 0) {
bp->b_src_copies++;
bp->b_src_deletes++;
} else {
bp->b_dst_copies++;
bp->b_dst_deletes++;
}
errs |= ERR_RESOLVABLE;
} else {
bp->b_unresolved++;
}
return (errs);
}
/*
* routine:
* copy
*
* purpose:
* to copy one file to another
*
* parameters:
* source file name
* destination file name
* desired modes
*
* returns:
* 0 OK
* else error mask, and a setting of copy_err_str
*
* notes:
* We try to preserve the holes in sparse files, by skipping over
* any holes that are at least MIN_HOLE bytes long. There are
* pathological cases where the hole detection test could become
* expensive, but for most blocks of most files we will fall out
* of the zero confirming loop in the first couple of bytes.
*/
static errmask_t
long *p, *e;
copy_err_str = 0;
/* open the input file */
#ifdef DBG_ERRORS
ifd = -1;
else
#endif
if (ifd < 0) {
return (ERR_PERM);
}
/*
* if we suspect a file may be sparse, we must process it
* a little more carefully, looking for holes and skipping
* over them in the output. If a file is not sparse, we
* can move through it at greater speed.
*/
else {
bsize = COPY_BSIZE;
}
/*
* if the target file already exists and we overwrite it without
* first ascertaining that there is enough room, we could wind
* up actually losing data. Try to determine how much space is
* available on the target file system, and if that is not enough
* for the source file, fail without even trying. If, however,
* the target file does not already exist, we have nothing to
* lose by just doing the copy without checking the space.
*/
#ifdef DBG_ERRORS
/* should we simulate an out-of-space situation */
#endif
if (ret == 0) {
return (ERR_FILES);
}
} else {
return (ERR_FILES);
}
}
/* create the output file */
#ifdef DBG_ERRORS
ofd = -1;
else
#endif
if (ofd < 0) {
return (ERR_PERM);
}
/* copy the data from the input file to the output file */
for (;;) {
#ifdef DBG_ERRORS
count = -1;
else
#endif
if (count <= 0)
break;
/*
* if the file might be sparse and we got an entire block,
* we should see if the block is all zeros
*/
while (p < e && *p == 0)
p++;
if (p == e) {
continue;
}
}
#ifdef DBG_ERRORS
ret = -1;
else
#endif
break;
}
}
if (count < 0) {
} else if (was_hole) {
/*
* if we skipped the last write because of a hole, we
* need to make sure that we write a single byte of null
* at the end of the file to update the file length.
*/
}
/*
* if the output file was botched, free up its space
*/
if (errs)
return (errs);
}
/*
* routine:
* checksparse
*
* purpose:
* to determine whether or not a file might be sparse, and if
* it is sparse, what the granularity of the holes is likely
* to be.
*
* parameters:
* file descriptor for file in question
*
* returns:
* 0 file does not appear to be sparse
* else block size for this file
*/
static int
{
/*
* unable to stat the file is very strange (since we got it
* open) but it probably isn't worth causing a fuss over.
* Return the conservative answer
*/
return (MIN_HOLE);
/*
* if the file doesn't have enough blocks to account for
* all of its bytes, there is a reasonable chance that it
* is sparse. This test is not perfect, in that it will
* fail to find holes in cases where the holes aren't
* numerous enough to componsent for the indirect blocks
* ... but losing those few holes is not going to be a
* big deal.
*/
return (statb.st_blksize);
else
return (0);
}