/*
* 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);
}