diff options
author | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
---|---|---|
committer | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
commit | 7c478bd95313f5f23a4c958a745db2134aa03244 (patch) | |
tree | c871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/cmd/filesync/action.c | |
download | illumos-gate-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz |
OpenSolaris Launch
Diffstat (limited to 'usr/src/cmd/filesync/action.c')
-rw-r--r-- | usr/src/cmd/filesync/action.c | 1258 |
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); +} |