#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"
bool_t need_super;
extern char *srcname;
extern char *dstname;
static errmask_t copy(char *, char *, int);
static int checksparse(int);
static char *copy_err_str;
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;
if (srcdst == opt_oneway) {
fp->f_flags |= F_CONFLICT;
fp->f_problem = gettext(PROB_prohibited);
bp->b_unresolved++;
return (ERR_UNRESOLVED);
}
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 ];
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);
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 (opt_everything == 0)
return (0);
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
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);
if (rc == 0) {
dp->f_mode = sp->f_mode;
ip->f_mode = sp->f_mode;
} else
errstr = gettext(PROB_chmod);
}
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
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);
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
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) {
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);
}
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
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);
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 (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 {
if (srcdst == OPT_SRC)
bp->b_src_misc++;
else
bp->b_dst_misc++;
fp->f_problem = 0;
errs |= ERR_RESOLVABLE;
}
return (errs);
}
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;
if (srcdst == opt_oneway) {
fp->f_problem = gettext(PROB_prohibited);
fp->f_flags |= F_CONFLICT;
bp->b_unresolved++;
return (ERR_UNRESOLVED);
}
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;
}
type = sp->f_type;
uid = sp->f_uid;
gid = sp->f_gid;
mode = sp->f_mode;
mtime = sp->f_modtime;
maj = sp->f_rd_maj;
min = sp->f_rd_min;
if ((dp->f_type == S_IFREG && sp->f_type == S_IFREG) &&
(dp->f_mode & 0200)) {
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 {
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 (sp->f_numacls)
do_acls = 1;
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
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
if (errno = dbg_chk_error(dst, 'u'))
rc = -1;
else
#endif
rc = opt_notouch ? 0 : unlink(dst);
}
if (rc != 0)
goto cant;
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");
}
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
if (errno = dbg_chk_error(dst, 'd'))
rc = -1;
else
#endif
rc = opt_notouch ? 0 : mkdir(dst, mode);
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
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
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
if (errno = dbg_chk_error(dst, 'd'))
rc = -1;
else
#endif
rc = opt_notouch ? 0
: mknod(dst, mode|type, makedev(maj, min));
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:
lp = find_link(fp, srcdst);
if (lp) {
src = full_name(lp, srcdst, OPT_BASE);
if (dp->f_type) {
if (!opt_quiet)
fprintf(stdout, "rm %s\n",
noblanks(dst));
errstr = gettext(PROB_unlink);
#ifdef DBG_ERRORS
if (errno = dbg_chk_error(dst, 'u'))
rc = -1;
else
#endif
rc = opt_notouch ? 0 : unlink(dst);
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
if (errno = dbg_chk_error(dst, 'l'))
rc = -1;
else
#endif
rc = opt_notouch ? 0 : link(src, dst);
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 {
lp->f_flags |= F_CONFLICT;
lp->f_problem = errstr;
break;
}
}
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);
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;
}
if (rc == 0 && opt_notouch == 0 && opt_mtime) {
newtimes.actime = mtime;
newtimes.modtime = mtime;
(void) utime(dst, &newtimes);
}
break;
default:
errstr = gettext(PROB_deal);
rc = -1;
}
if (rc == 0 && (do_chmod || do_chown || do_chgrp || do_acls)) {
rc = do_like(fp, srcdst, FALSE);
errstr = fp->f_problem;
errs |= rc;
}
if (rc == 0)
if (!opt_notouch) {
errstr = gettext(PROB_restat);
#ifdef DBG_ERRORS
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 {
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 {
if (srcdst == OPT_SRC)
bp->b_src_copies++;
else
bp->b_dst_copies++;
errs |= ERR_RESOLVABLE;
}
return (errs);
}
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 = "???";
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
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
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) {
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);
}
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;
if (srcdst == opt_oneway) {
fp->f_problem = gettext(PROB_prohibited);
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
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 (rc == 0)
if (!opt_notouch) {
errstr = gettext(PROB_restat);
#ifdef DBG_ERRORS
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 {
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);
}
static errmask_t
copy(char *src, char *dst, int mode)
{ int ifd, ofd, count, ret;
long *p, *e;
long long length;
errmask_t errs = 0;
int bsize;
bool_t sparse;
bool_t was_hole = FALSE;
long inbuf[ COPY_BSIZE/4 ];
struct stat statbuf;
struct statvfs statvsbuf;
copy_err_str = 0;
#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);
}
bsize = checksparse(ifd);
if (bsize > 0 && bsize <= COPY_BSIZE)
sparse = TRUE;
else {
sparse = FALSE;
bsize = COPY_BSIZE;
}
ret = statvfs(dst, &statvsbuf);
if (ret == 0 && statvsbuf.f_frsize != 0) {
#ifdef DBG_ERRORS
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;
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);
}
}
#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);
}
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 (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) {
(void) lseek(ofd, (off_t)-1, SEEK_CUR);
(void) write(ofd, "", 1);
}
if (errs)
ftruncate(ofd, (off_t) 0);
close(ifd);
close(ofd);
return (errs);
}
static int
checksparse(int fd)
{
struct stat statb;
if (fstat(fd, &statb) < 0)
return (MIN_HOLE);
if (statb.st_size > 512 * statb.st_blocks)
return (statb.st_blksize);
else
return (0);
}