summaryrefslogtreecommitdiff
path: root/usr/src/cmd/filesync/action.c
diff options
context:
space:
mode:
authorstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
committerstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
commit7c478bd95313f5f23a4c958a745db2134aa03244 (patch)
treec871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/cmd/filesync/action.c
downloadillumos-gate-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz
OpenSolaris Launch
Diffstat (limited to 'usr/src/cmd/filesync/action.c')
-rw-r--r--usr/src/cmd/filesync/action.c1258
1 files changed, 1258 insertions, 0 deletions
diff --git a/usr/src/cmd/filesync/action.c b/usr/src/cmd/filesync/action.c
new file mode 100644
index 0000000000..6f53ffd331
--- /dev/null
+++ b/usr/src/cmd/filesync/action.c
@@ -0,0 +1,1258 @@
+/*
+ * 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:
+ * 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 <sys/mkdev.h>
+#include <sys/statvfs.h>
+
+#include "filesync.h"
+#include "database.h"
+#include "messages.h"
+#include "debug.h"
+
+/*
+ * globals and importeds
+ */
+bool_t need_super; /* warn user that we can't fix ownership */
+extern char *srcname; /* file we are emulating */
+extern char *dstname; /* file we are updating */
+
+/*
+ * locals
+ */
+static errmask_t copy(char *, char *, int);
+static int checksparse(int);
+static char *copy_err_str; /* what went wrong w/copy */
+
+/*
+ * routine:
+ * do_like
+ *
+ * purpose:
+ * to propagate ownership and protection changes between
+ * one existing file and another.
+ *
+ * parameters:
+ * file pointer
+ * src/dst indication for who needs to change
+ * 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.
+ */
+errmask_t
+do_like(struct file *fp, side_t srcdst, bool_t do_stats)
+{ char *dst;
+ int rc = 0;
+ int do_chown, do_chmod, do_chgrp, do_acls;
+ errmask_t errs = 0;
+ char *errstr = 0;
+ struct base *bp;
+ struct fileinfo *sp;
+ struct fileinfo *dp;
+ struct fileinfo *ip;
+ extern int errno;
+
+ bp = fp->f_base;
+
+ /* see if this is a forbidden propagation */
+ if (srcdst == opt_oneway) {
+ fp->f_flags |= F_CONFLICT;
+ fp->f_problem = gettext(PROB_prohibited);
+ bp->b_unresolved++;
+ return (ERR_UNRESOLVED);
+ }
+
+
+ /* get info about source and target files */
+ if (srcdst == OPT_SRC) {
+ sp = &fp->f_info[ OPT_DST ];
+ dp = &fp->f_info[ OPT_SRC ];
+ dst = srcname;
+ } else {
+ sp = &fp->f_info[ OPT_SRC ];
+ dp = &fp->f_info[ OPT_DST ];
+ dst = dstname;
+ }
+ ip = &fp->f_info[ OPT_BASE ];
+
+ /* figure out what needs fixing */
+ do_chmod = (sp->f_mode != dp->f_mode);
+ do_chown = (sp->f_uid != dp->f_uid);
+ do_chgrp = (sp->f_gid != dp->f_gid);
+ do_acls = ((fp->f_srcdiffs|fp->f_dstdiffs) & D_FACLS);
+
+ /*
+ * 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)
+ errstr = gettext(PROB_chown);
+ else if (my_uid != dp->f_uid) {
+ if (do_chmod)
+ errstr = gettext(PROB_chmod);
+ else if (do_acls)
+ errstr = gettext(PROB_chacl);
+ else if (do_chgrp)
+ errstr = gettext(PROB_chgrp);
+ }
+#ifdef ACL_UID_BUG
+ else if (do_acls && my_gid != dp->f_gid)
+ errstr = gettext(PROB_botch);
+#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 (opt_debug & DBG_RECON) {
+ fprintf(stderr, "RECO: do_like %s (", dst);
+ if (do_chmod)
+ fprintf(stderr, "chmod ");
+ if (do_acls)
+ fprintf(stderr, "acls ");
+ if (do_chown)
+ fprintf(stderr, "chown ");
+ if (do_chgrp)
+ fprintf(stderr, "chgrp ");
+ fprintf(stderr, ")\n");
+ }
+
+ if (do_chmod) {
+ if (!opt_quiet)
+ fprintf(stdout, "chmod %o %s\n", sp->f_mode,
+ noblanks(dst));
+
+#ifdef DBG_ERRORS
+ /* should we simulate a chmod failure */
+ if (errno = dbg_chk_error(dst, 'p'))
+ rc = -1;
+ else
+#endif
+ rc = opt_notouch ? 0 : chmod(dst, sp->f_mode);
+
+ if (opt_debug & DBG_RECON)
+ fprintf(stderr, "RECO: do_chmod %o -> %d(%d)\n",
+ sp->f_mode, rc, errno);
+
+ /* update dest and baseline to reflect the change */
+ if (rc == 0) {
+ dp->f_mode = sp->f_mode;
+ ip->f_mode = sp->f_mode;
+ } else
+ errstr = gettext(PROB_chmod);
+ }
+
+ /*
+ * see if we need to fix the acls
+ */
+ if (rc == 0 && do_acls) {
+ if (!opt_quiet)
+ fprintf(stdout, "setfacl %s %s\n",
+ show_acls(sp->f_numacls, sp->f_acls),
+ noblanks(dst));
+
+#ifdef DBG_ERRORS
+ /* should we simulate a set acl failure */
+ if (errno = dbg_chk_error(dst, 'a'))
+ rc = -1;
+ else
+#endif
+ rc = opt_notouch ? 0 : set_acls(dst, sp);
+
+ if (opt_debug & DBG_RECON)
+ fprintf(stderr, "RECO: do_acls %d -> %d(%d)\n",
+ sp->f_numacls, rc, errno);
+
+ /* update dest and baseline to reflect the change */
+ if (rc == 0) {
+ dp->f_numacls = sp->f_numacls;
+ dp->f_acls = sp->f_acls;
+ ip->f_numacls = sp->f_numacls;
+ ip->f_acls = sp->f_acls;
+#ifdef ACL_UID_BUG
+ /* SETFACL changes a file's UID/GID */
+ if (my_uid != dp->f_uid) {
+ do_chown = 1;
+ dp->f_uid = my_uid;
+ }
+ if (my_gid != dp->f_gid) {
+ do_chgrp = 1;
+ dp->f_gid = my_gid;
+ }
+#endif
+ } else if (errno == ENOSYS) {
+ /*
+ * if the file system doesn't support ACLs
+ * we should just pretend we never saw them
+ */
+ fprintf(stderr, gettext(WARN_noacls), dst);
+ ip->f_numacls = 0;
+ sp->f_numacls = 0;
+ dp->f_numacls = 0;
+ rc = 0;
+ } else
+ errstr = gettext(PROB_chacl);
+ }
+
+ /*
+ * see if we need to fix the ownership
+ */
+ if (rc == 0 && (do_chown || do_chgrp)) {
+ if (do_chown)
+ fprintf(stdout, "chown %ld %s; ",
+ sp->f_uid, noblanks(dst));
+ if (do_chgrp)
+ fprintf(stdout, "chgrp %ld %s",
+ sp->f_gid, noblanks(dst));
+
+ fprintf(stdout, "\n");
+
+#ifdef DBG_ERRORS
+ /* should we simulate a chown failure */
+ if (errno = dbg_chk_error(dst, 'O'))
+ rc = -1;
+ else
+#endif
+ rc = opt_notouch ? 0 : lchown(dst, sp->f_uid, sp->f_gid);
+
+ if (opt_debug & DBG_RECON)
+ fprintf(stderr, "RECO: do_chown %ld %ld -> %d(%d)\n",
+ sp->f_uid, sp->f_gid, rc, errno);
+
+ /* update the destination to reflect changes */
+ if (rc == 0) {
+ dp->f_uid = sp->f_uid;
+ dp->f_gid = sp->f_gid;
+ ip->f_uid = sp->f_uid;
+ ip->f_gid = sp->f_gid;
+ } else {
+ if (errno == EPERM) {
+ need_super = TRUE;
+ if (opt_everything == 0)
+ return (0);
+ }
+
+ if (rc != 0)
+ errstr = gettext(do_chown ?
+ 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 (rc == 0 && do_stats)
+ link_update(fp, srcdst);
+
+nogood:
+ if (!do_stats)
+ return (errs);
+
+ if (rc != 0) {
+ fprintf(stderr, gettext(ERR_cannot), errstr, dst);
+ fp->f_problem = errstr;
+ fp->f_flags |= F_CONFLICT;
+ bp->b_unresolved++;
+ errs |= ERR_PERM | ERR_UNRESOLVED;
+ } else {
+ /*
+ * it worked, so update the baseline and statistics
+ */
+ if (srcdst == OPT_SRC)
+ bp->b_src_misc++;
+ else
+ bp->b_dst_misc++;
+
+ fp->f_problem = 0;
+ errs |= ERR_RESOLVABLE;
+ }
+
+ return (errs);
+}
+
+/*
+ * routine:
+ * do_copy
+ *
+ * purpose:
+ * to propagate a creation or change
+ *
+ * parameters:
+ * file pointer
+ * src/dst indication for who gets the copy
+ *
+ * returns:
+ * error mask
+ *
+ * note:
+ * after any successful operation we update the stat/info
+ * 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).
+ */
+errmask_t
+do_copy(struct file *fp, side_t srcdst)
+{ char *src, *dst;
+ char cmdbuf[ MAX_PATH + MAX_NAME ];
+ int mode, maj, min, type;
+ uid_t uid;
+ gid_t gid;
+ 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;
+ char *errstr = "???";
+ errmask_t errs = 0;
+ struct base *bp;
+ struct file *lp;
+ struct fileinfo *sp, *dp;
+ struct utimbuf newtimes;
+ struct stat statb;
+
+ bp = fp->f_base;
+
+ /* see if this is a forbidden propagation */
+ if (srcdst == opt_oneway) {
+ fp->f_problem = gettext(PROB_prohibited);
+ fp->f_flags |= F_CONFLICT;
+ bp->b_unresolved++;
+ return (ERR_UNRESOLVED);
+ }
+
+ /* figure out who is the source and who is the destination */
+ if (srcdst == OPT_SRC) {
+ sp = &fp->f_info[ OPT_DST ];
+ dp = &fp->f_info[ OPT_SRC ];
+ src = dstname;
+ dst = srcname;
+ } else {
+ sp = &fp->f_info[ OPT_SRC ];
+ dp = &fp->f_info[ OPT_DST ];
+ src = srcname;
+ dst = dstname;
+ }
+
+ /* note information about the file to be created */
+ type = sp->f_type; /* type of the new file */
+ uid = sp->f_uid; /* owner of the new file */
+ gid = sp->f_gid; /* group of the new file */
+ mode = sp->f_mode; /* modes for the new file */
+ mtime = sp->f_modtime; /* modtime (if preserving) */
+ maj = sp->f_rd_maj; /* major (if it is a device) */
+ min = sp->f_rd_min; /* minor (if it is a device) */
+
+ /*
+ * creating a file does not guarantee it will get the desired
+ * modes, uid and gid. If the file already exists, it will
+ * retain its old ownership and protection. If my UID/GID
+ * 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 ((dp->f_type == S_IFREG && sp->f_type == S_IFREG) &&
+ (dp->f_mode & 0200)) {
+ /* if the file already exists */
+ if (dp->f_uid != uid)
+ do_chown = 1;
+
+ if (dp->f_gid != gid)
+ do_chgrp = 1;
+
+ if (dp->f_mode != mode)
+ do_chmod = 1;
+ } else {
+ /* if we will be creating a new file */
+ do_create = 1;
+ if (dp->f_type)
+ do_unlink = 1;
+ if (uid != my_uid)
+ do_chown = 1;
+ if (gid != my_gid)
+ do_chgrp = 1;
+ }
+
+ /*
+ * if the source has acls, we will surely have to set them for dest
+ */
+ if (sp->f_numacls)
+ 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 (dp->f_type == S_IFDIR) {
+ if (!opt_quiet)
+ fprintf(stdout, "rmdir %s\n", noblanks(dst));
+
+ errstr = gettext(PROB_rmdir);
+#ifdef DBG_ERRORS
+ /* should we simulate a rmdir failure */
+ if (errno = dbg_chk_error(dst, 'D'))
+ rc = -1;
+ else
+#endif
+ rc = opt_notouch ? 0 : rmdir(dst);
+ } else {
+ if (!opt_quiet)
+ fprintf(stdout, "rm %s\n", noblanks(dst));
+
+ errstr = gettext(PROB_unlink);
+#ifdef DBG_ERRORS
+ /* should we simulate a unlink failure */
+ if (errno = dbg_chk_error(dst, 'u'))
+ rc = -1;
+ else
+#endif
+ rc = opt_notouch ? 0 : unlink(dst);
+ }
+
+ if (rc != 0)
+ goto cant;
+
+ /* note that this file no longer exists */
+ dp->f_type = 0;
+ dp->f_mode = 0;
+ }
+
+ if (opt_debug & DBG_RECON) {
+ fprintf(stderr, "RECO: do_copy %s %s (", src, dst);
+ if (do_unlink)
+ fprintf(stderr, "unlink ");
+ if (do_chmod)
+ fprintf(stderr, "chmod ");
+ if (do_acls)
+ fprintf(stderr, "acls ");
+ if (do_chown)
+ fprintf(stderr, "chown ");
+ if (do_chgrp)
+ fprintf(stderr, "chgrp ");
+ fprintf(stderr, ")\n");
+ }
+
+ /*
+ * 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) {
+ fprintf(stdout, "mkdir %s;", noblanks(dst));
+ fprintf(stdout, " chmod %o %s;\n", mode, noblanks(dst));
+ }
+
+ errstr = gettext(PROB_mkdir);
+
+#ifdef DBG_ERRORS
+ /* should we simulate a mkdir failure */
+ if (errno = dbg_chk_error(dst, 'd'))
+ rc = -1;
+ else
+#endif
+ rc = opt_notouch ? 0 : mkdir(dst, mode);
+
+ /* update stat with what we have just created */
+ if (rc == 0) {
+ dp->f_type = S_IFDIR;
+ dp->f_uid = my_uid;
+ dp->f_gid = my_gid;
+ dp->f_mode = mode;
+ }
+
+ break;
+
+ case S_IFLNK:
+ errstr = gettext(PROB_readlink);
+#ifdef DBG_ERRORS
+ /* should we simulate a symlink read failure */
+ if (errno = dbg_chk_error(dst, 'r'))
+ rc = -1;
+ else
+#endif
+ rc = readlink(src, cmdbuf, sizeof (cmdbuf));
+ if (rc > 0) {
+ cmdbuf[rc] = 0;
+ if (!opt_quiet) {
+ fprintf(stdout, "ln -s %s", noblanks(cmdbuf));
+ fprintf(stdout, " %s;\n", noblanks(dst));
+ }
+ errstr = gettext(PROB_symlink);
+#ifdef DBG_ERRORS
+ /* should we simulate a symlink failure */
+ if (errno = dbg_chk_error(dst, 'l'))
+ rc = -1;
+ else
+#endif
+ rc = opt_notouch ? 0 : symlink(cmdbuf, dst);
+
+ if (rc == 0)
+ dp->f_type = S_IFLNK;
+ }
+ break;
+
+ case S_IFBLK:
+ case S_IFCHR:
+ if (!opt_quiet)
+ fprintf(stdout, "mknod %s %s %d %d\n", noblanks(dst),
+ (type == S_IFBLK) ? "b" : "c", maj, min);
+
+ errstr = gettext(PROB_mknod);
+#ifdef DBG_ERRORS
+ /* should we simulate a mknod failure */
+ if (errno = dbg_chk_error(dst, 'd'))
+ rc = -1;
+ else
+#endif
+ rc = opt_notouch ? 0
+ : mknod(dst, mode|type, makedev(maj, min));
+
+ /* update stat with what we have just created */
+ if (rc == 0) {
+ dp->f_type = type;
+ dp->f_uid = my_uid;
+ dp->f_gid = my_gid;
+ dp->f_mode = 0666;
+
+ if (dp->f_mode != mode)
+ 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.
+ */
+ lp = find_link(fp, srcdst);
+ if (lp) {
+ /* figure out name of existing file */
+ src = full_name(lp, srcdst, OPT_BASE);
+
+ /*
+ * if file already exists, it must be deleted
+ */
+ if (dp->f_type) {
+ if (!opt_quiet)
+ fprintf(stdout, "rm %s\n",
+ noblanks(dst));
+
+ errstr = gettext(PROB_unlink);
+#ifdef DBG_ERRORS
+ /* should we simulate a unlink failure */
+ if (errno = dbg_chk_error(dst, 'u'))
+ rc = -1;
+ else
+#endif
+ rc = opt_notouch ? 0 : unlink(dst);
+
+ /*
+ * 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) {
+ lp->f_flags |= F_CONFLICT;
+ lp->f_problem = gettext(PROB_link);
+ goto cant;
+ }
+ }
+
+ if (!opt_quiet) {
+ fprintf(stdout, "ln %s", noblanks(src));
+ fprintf(stdout, " %s\n", noblanks(dst));
+ }
+ errstr = gettext(PROB_link);
+
+#ifdef DBG_ERRORS
+ /* should we simulate a link failure */
+ if (errno = dbg_chk_error(dst, 'l'))
+ rc = -1;
+ else
+#endif
+ rc = opt_notouch ? 0 : link(src, dst);
+
+ /*
+ * if this is a link, there is no reason to worry
+ * about ownership and modes, they are automatic
+ */
+ do_chown = 0; do_chgrp = 0; do_chmod = 0; do_acls = 0;
+ if (rc == 0) {
+ dp->f_type = type;
+ dp->f_uid = uid;
+ dp->f_gid = gid;
+ dp->f_mode = mode;
+ 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.
+ */
+ lp->f_flags |= F_CONFLICT;
+ lp->f_problem = errstr;
+ 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) {
+ fprintf(stdout, "cp %s", noblanks(src));
+ fprintf(stdout, " %s\n", noblanks(dst));
+ }
+ rc = opt_notouch ? 0 : copy(src, dst, mode);
+ if (rc != 0) {
+ errs |= rc;
+ if (copy_err_str)
+ errstr = copy_err_str;
+ else
+ errstr = gettext(PROB_copy);
+
+ /*
+ * 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)
+ unlink(dst);
+ } else if (dp->f_mode == 0) {
+ dp->f_type = S_IFREG;
+ dp->f_uid = my_uid;
+ dp->f_gid = my_gid;
+ dp->f_mode = mode;
+
+ /* FIX: inode number is still wrong */
+ }
+
+ /* for normal files we have an option to preserve mod time */
+ if (rc == 0 && opt_notouch == 0 && opt_mtime) {
+ newtimes.actime = mtime;
+ newtimes.modtime = mtime;
+
+ /* ignore the error return on this one */
+ (void) utime(dst, &newtimes);
+ }
+ break;
+
+ default:
+ errstr = gettext(PROB_deal);
+ 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.
+ */
+ if (rc == 0 && (do_chmod || do_chown || do_chgrp || do_acls)) {
+ rc = do_like(fp, srcdst, FALSE);
+ errstr = fp->f_problem;
+ errs |= rc;
+ }
+
+ /*
+ * finish off by re-stating the destination and using that to
+ * update the baseline. If we were completely successful in
+ * our chowns/chmods, stating the destination will confirm it.
+ * 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) {
+ errstr = gettext(PROB_restat);
+
+#ifdef DBG_ERRORS
+ /* should we simulate a restat failure */
+ if (errno = dbg_chk_error(dst, 'R'))
+ rc = -1;
+ else
+#endif
+ rc = lstat(dst, &statb);
+
+ if (rc == 0) {
+ note_info(fp, &statb, srcdst);
+ link_update(fp, srcdst);
+ if (do_acls)
+ (void) get_acls(dst, dp);
+ update_info(fp, srcdst);
+ }
+ } 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.
+ */
+ if (dp->f_ino == 0 || dp->f_nlink == 0) {
+ dp->f_ino = sp->f_ino;
+ dp->f_nlink = 1;
+ }
+ }
+
+cant: if (rc != 0) {
+ fprintf(stderr, gettext(ERR_cannot), errstr, dst);
+ bp->b_unresolved++;
+ fp->f_flags |= F_CONFLICT;
+ fp->f_problem = errstr;
+ if (errs == 0)
+ errs = ERR_PERM;
+ errs |= ERR_UNRESOLVED;
+ } else {
+ /* update the statistics */
+ if (srcdst == OPT_SRC)
+ 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
+ * src/dst indication for which side gets changed
+ *
+ * returns:
+ * error mask
+ */
+errmask_t
+do_remove(struct file *fp, side_t srcdst)
+{ char *name;
+ int rc;
+ struct base *bp = fp->f_base;
+ errmask_t errs = 0;
+ char *errstr = "???";
+
+ /* see if this is a forbidden propagation */
+ if (srcdst == opt_oneway) {
+ fp->f_problem = gettext(PROB_prohibited);
+ fp->f_flags |= F_CONFLICT;
+ bp->b_unresolved++;
+ return (ERR_UNRESOLVED);
+ }
+
+ name = (srcdst == OPT_SRC) ? srcname : dstname;
+
+ if (fp->f_info[0].f_type == S_IFDIR) {
+ if (!opt_quiet)
+ fprintf(stdout, "rmdir %s\n", noblanks(name));
+
+ errstr = gettext(PROB_rmdir);
+
+#ifdef DBG_ERRORS
+ /* should we simulate a rmdir failure */
+ if (errno = dbg_chk_error(name, 'D'))
+ rc = -1;
+ else
+#endif
+ rc = opt_notouch ? 0 : rmdir(name);
+ } else {
+ if (!opt_quiet)
+ fprintf(stdout, "rm %s\n", noblanks(name));
+
+ errstr = gettext(PROB_unlink);
+
+#ifdef DBG_ERRORS
+ /* should we simulate an unlink failure */
+ if (errno = dbg_chk_error(name, 'u'))
+ rc = -1;
+ else
+#endif
+ rc = opt_notouch ? 0 : unlink(name);
+ }
+
+ if (opt_debug & DBG_RECON)
+ fprintf(stderr, "RECO: do_remove %s -> %d(%d)\n",
+ name, rc, errno);
+
+ if (rc == 0) {
+ /* tell any other hard links that one has gone away */
+ fp->f_info[srcdst].f_nlink--;
+ link_update(fp, srcdst);
+
+ fp->f_flags |= F_REMOVE;
+ if (srcdst == OPT_SRC)
+ fp->f_base->b_src_deletes++;
+ else
+ fp->f_base->b_dst_deletes++;
+ errs |= ERR_RESOLVABLE;
+ } else {
+ fprintf(stderr, gettext(ERR_cannot), errstr, name);
+ fp->f_problem = errstr;
+ fp->f_flags |= F_CONFLICT;
+ bp->b_unresolved++;
+ errs |= ERR_PERM | ERR_UNRESOLVED;
+ }
+
+ return (errs);
+}
+
+/*
+ * routine:
+ * do_rename
+ *
+ * purpose:
+ * to propagate a rename
+ *
+ * parameters:
+ * file pointer for the new name
+ * src/dst indication for which side gets changed
+ *
+ * returns:
+ * error mask
+ */
+errmask_t
+do_rename(struct file *fp, side_t srcdst)
+{ int rc;
+ struct file *pp = fp->f_previous;
+ struct base *bp = fp->f_base;
+ errmask_t errs = 0;
+ char *errstr = "???";
+ char *newname;
+ char *oldname;
+ struct stat statb;
+
+ /* see if this is a forbidden propagation */
+ if (srcdst == opt_oneway) {
+ fp->f_problem = gettext(PROB_prohibited);
+
+ /* if we can't resolve the TO, the FROM is also unresolved */
+ pp->f_problem = gettext(PROB_prohibited);
+ pp->f_flags |= F_CONFLICT;
+ bp->b_unresolved++;
+ return (ERR_UNRESOLVED);
+ }
+
+ newname = (srcdst == OPT_SRC) ? srcname : dstname;
+ oldname = full_name(pp, srcdst, OPT_BASE);
+
+ if (!opt_quiet)
+ fprintf(stdout, "%s %s %s\n",
+ (fp->f_info[0].f_type == S_IFDIR) ? "mvdir" : "mv",
+ noblanks(oldname), noblanks(newname));
+
+#ifdef DBG_ERRORS
+ /* should we simulate a rename failure */
+ if (errno = dbg_chk_error(oldname, 'm'))
+ rc = -1;
+ else
+#endif
+ rc = opt_notouch ? 0 : rename(oldname, newname);
+
+ if (opt_debug & DBG_RECON)
+ fprintf(stderr, "RECO: do_rename %s %s -> %d(%d)\n",
+ oldname, newname, rc, errno);
+
+ /* if we succeed, update the baseline */
+ if (rc == 0)
+ if (!opt_notouch) {
+ errstr = gettext(PROB_restat);
+
+#ifdef DBG_ERRORS
+ /* should we simulate a restat failure */
+ if (errno = dbg_chk_error(newname, 'S'))
+ rc = -1;
+ else
+#endif
+ rc = lstat(newname, &statb);
+
+ if (rc == 0) {
+ note_info(fp, &statb, srcdst);
+ link_update(fp, srcdst);
+ update_info(fp, srcdst);
+ }
+ } else {
+ /*
+ * BOGOSITY ALERT
+ * in order for link tests to work in notouch
+ * mode we have to dummy up some updated status
+ */
+ fp->f_info[srcdst].f_ino = pp->f_info[srcdst].f_ino;
+ fp->f_info[srcdst].f_nlink = pp->f_info[srcdst].f_nlink;
+ fp->f_info[srcdst].f_type = pp->f_info[srcdst].f_type;
+ fp->f_info[srcdst].f_size = pp->f_info[srcdst].f_size;
+ fp->f_info[srcdst].f_mode = pp->f_info[srcdst].f_mode;
+ fp->f_info[srcdst].f_uid = pp->f_info[srcdst].f_uid;
+ fp->f_info[srcdst].f_gid = pp->f_info[srcdst].f_gid;
+ update_info(fp, srcdst);
+ }
+ else
+ errstr = gettext(PROB_rename2);
+
+ if (rc == 0) {
+ pp->f_flags |= F_REMOVE;
+
+ if (srcdst == OPT_SRC) {
+ bp->b_src_copies++;
+ bp->b_src_deletes++;
+ } else {
+ bp->b_dst_copies++;
+ bp->b_dst_deletes++;
+ }
+ errs |= ERR_RESOLVABLE;
+ } else {
+ fprintf(stderr, gettext(ERR_cannot), errstr, oldname);
+
+ bp->b_unresolved++;
+ fp->f_flags |= F_CONFLICT;
+ pp->f_flags |= F_CONFLICT;
+
+ fp->f_problem = errstr;
+ pp->f_problem = gettext(PROB_rename);
+
+ errs |= ERR_PERM | ERR_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
+copy(char *src, char *dst, int mode)
+{ int ifd, ofd, count, ret;
+ long *p, *e;
+ long long length; /* total size of file */
+ errmask_t errs = 0;
+ int bsize; /* block-size for file */
+ bool_t sparse; /* file may be sparse */
+ bool_t was_hole = FALSE; /* file ends with hole */
+ long inbuf[ COPY_BSIZE/4 ]; /* long to speed checks */
+ struct stat statbuf; /* info on source file */
+ struct statvfs statvsbuf; /* info on target fs */
+
+ copy_err_str = 0;
+
+ /* open the input file */
+#ifdef DBG_ERRORS
+ if (opt_errors && dbg_chk_error(src, 'o'))
+ ifd = -1;
+ else
+#endif
+ ifd = open(src, O_RDONLY);
+
+ if (ifd < 0) {
+ copy_err_str = gettext(PROB_copyin);
+ 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.
+ */
+ bsize = checksparse(ifd);
+ if (bsize > 0 && bsize <= COPY_BSIZE)
+ sparse = TRUE;
+ else {
+ sparse = FALSE;
+ 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.
+ */
+ ret = statvfs(dst, &statvsbuf);
+ if (ret == 0 && statvsbuf.f_frsize != 0) {
+#ifdef DBG_ERRORS
+ /* should we simulate an out-of-space situation */
+ if ((length = dbg_chk_error(dst, 'Z')) == 0)
+#endif
+ length = statvsbuf.f_bavail * statvsbuf.f_frsize;
+
+ ret = fstat(ifd, &statbuf);
+ if (ret == 0) {
+ length /= 512; /* st_blocks in 512s */
+ if (length < statbuf.st_blocks) {
+ copy_err_str = gettext(PROB_space);
+ close(ifd);
+ return (ERR_FILES);
+ }
+ } else {
+ copy_err_str = gettext(PROB_restat);
+ close(ifd);
+ return (ERR_FILES);
+ }
+ }
+
+ /* create the output file */
+#ifdef DBG_ERRORS
+ if (opt_errors && dbg_chk_error(dst, 'c'))
+ ofd = -1;
+ else
+#endif
+ ofd = creat(dst, mode);
+
+ if (ofd < 0) {
+ close(ifd);
+ copy_err_str = gettext(PROB_copyout);
+ return (ERR_PERM);
+ }
+
+ /* copy the data from the input file to the output file */
+ for (;;) {
+#ifdef DBG_ERRORS
+ if (opt_errors && dbg_chk_error(dst, 'r'))
+ count = -1;
+ else
+#endif
+ count = read(ifd, (char *) inbuf, bsize);
+ 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
+ */
+ if (sparse && count == bsize) {
+ p = inbuf; e = &inbuf[count/4];
+ while (p < e && *p == 0)
+ p++;
+ if (p == e) {
+ (void) lseek(ofd, (off_t) count, SEEK_CUR);
+ was_hole = TRUE;
+ continue;
+ }
+ }
+ was_hole = FALSE;
+
+#ifdef DBG_ERRORS
+ if (opt_errors && dbg_chk_error(dst, 'w'))
+ ret = -1;
+ else
+#endif
+ ret = write(ofd, (char *) inbuf, count);
+
+ if (ret != count) {
+ errs = ERR_FILES;
+ copy_err_str = gettext(PROB_write);
+ break;
+ }
+ }
+
+ if (count < 0) {
+ copy_err_str = gettext(PROB_read);
+ errs = ERR_FILES;
+ } 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.
+ */
+ (void) lseek(ofd, (off_t)-1, SEEK_CUR);
+ (void) write(ofd, "", 1);
+ }
+
+ /*
+ * if the output file was botched, free up its space
+ */
+ if (errs)
+ ftruncate(ofd, (off_t) 0);
+
+ close(ifd);
+ close(ofd);
+ 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
+checksparse(int fd)
+{
+ struct stat statb;
+
+ /*
+ * 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
+ */
+ if (fstat(fd, &statb) < 0)
+ 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.
+ */
+ if (statb.st_size > 512 * statb.st_blocks)
+ return (statb.st_blksize);
+ else
+ return (0);
+}