#include <sys/stat.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <fts.h>
#include <limits.h>
#include <inttypes.h>
#include <search.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "extern.h"
#define FLIST_CHUNK_SIZE (1024)
#define FLIST_TOP_LEVEL 0x0001
#define FLIST_MODE_SAME 0x0002
#define FLIST_RDEV_SAME 0x0004
#define FLIST_UID_SAME 0x0008
#define FLIST_GID_SAME 0x0010
#define FLIST_NAME_SAME 0x0020
#define FLIST_NAME_LONG 0x0040
#define FLIST_TIME_SAME 0x0080
static int
flist_cmp(const void *p1, const void *p2)
{
const struct flist *f1 = p1, *f2 = p2;
return strcmp(f1->wpath, f2->wpath);
}
static int
flist_dedupe(struct flist **fl, size_t *sz)
{
size_t i, j;
struct flist *new;
struct flist *f, *fnext;
if (*sz == 0)
return 1;
new = calloc(*sz, sizeof(struct flist));
if (new == NULL) {
ERR("calloc");
return 0;
}
for (i = j = 0; i < *sz - 1; i++) {
f = &(*fl)[i];
fnext = &(*fl)[i + 1];
if (strcmp(f->wpath, fnext->wpath)) {
new[j++] = *f;
continue;
}
if (strcmp(f->path, fnext->path) == 0) {
new[j++] = *f;
i++;
WARNX("%s: duplicate path: %s",
f->wpath, f->path);
free(fnext->path);
free(fnext->link);
fnext->path = fnext->link = NULL;
continue;
}
ERRX("%s: duplicate working path for "
"possibly different file: %s, %s",
f->wpath, f->path, fnext->path);
free(new);
return 0;
}
if (i == *sz - 1)
new[j++] = (*fl)[i];
free(*fl);
*fl = new;
*sz = j;
assert(*sz);
return 1;
}
static void
flist_topdirs(struct sess *sess, struct flist *fl, size_t flsz)
{
size_t i;
const char *cp;
if (!sess->opts->recursive)
return;
if (flsz && strcmp(fl[0].wpath, ".")) {
for (i = 0; i < flsz; i++) {
if (!S_ISDIR(fl[i].st.mode))
continue;
cp = strchr(fl[i].wpath, '/');
if (cp != NULL && cp[1] != '\0')
continue;
fl[i].st.flags |= FLSTAT_TOP_DIR;
LOG4("%s: top-level", fl[i].wpath);
}
} else if (flsz) {
fl[0].st.flags |= FLSTAT_TOP_DIR;
LOG4("%s: top-level", fl[0].wpath);
}
}
static int
flist_fts_check(struct sess *sess, FTSENT *ent)
{
if (ent->fts_info == FTS_F ||
ent->fts_info == FTS_D ||
ent->fts_info == FTS_SL ||
ent->fts_info == FTS_SLNONE)
return 1;
if (ent->fts_info == FTS_DC) {
WARNX("%s: directory cycle", ent->fts_path);
} else if (ent->fts_info == FTS_DNR) {
errno = ent->fts_errno;
WARN("%s: unreadable directory", ent->fts_path);
} else if (ent->fts_info == FTS_DOT) {
WARNX("%s: skipping dot-file", ent->fts_path);
} else if (ent->fts_info == FTS_ERR) {
errno = ent->fts_errno;
WARN("%s", ent->fts_path);
} else if (ent->fts_info == FTS_DEFAULT) {
if ((sess->opts->devices && (S_ISBLK(ent->fts_statp->st_mode) ||
S_ISCHR(ent->fts_statp->st_mode))) ||
(sess->opts->specials &&
(S_ISFIFO(ent->fts_statp->st_mode) ||
S_ISSOCK(ent->fts_statp->st_mode)))) {
return 1;
}
WARNX("%s: skipping special", ent->fts_path);
} else if (ent->fts_info == FTS_NS) {
errno = ent->fts_errno;
WARN("%s: could not stat", ent->fts_path);
}
return 0;
}
static void
flist_copy_stat(struct flist *f, const struct stat *st)
{
f->st.mode = st->st_mode;
f->st.uid = st->st_uid;
f->st.gid = st->st_gid;
f->st.size = st->st_size;
f->st.mtime = st->st_mtime;
f->st.rdev = st->st_rdev;
}
void
flist_free(struct flist *f, size_t sz)
{
size_t i;
if (f == NULL)
return;
for (i = 0; i < sz; i++) {
free(f[i].path);
free(f[i].link);
}
free(f);
}
int
flist_send(struct sess *sess, int fdin, int fdout, const struct flist *fl,
size_t flsz)
{
size_t i, sz, gidsz = 0, uidsz = 0;
uint8_t flag;
const struct flist *f;
const char *fn;
struct ident *gids = NULL, *uids = NULL;
int rc = 0;
LOG2("sending file metadata list: %zu", flsz);
for (i = 0; i < flsz; i++) {
f = &fl[i];
fn = f->wpath;
sz = strlen(f->wpath);
assert(sz > 0);
assert(sz < INT32_MAX);
if (sess->mplex_reads &&
io_read_check(fdin) &&
!io_read_flush(sess, fdin)) {
ERRX1("io_read_flush");
goto out;
}
flag = FLIST_NAME_LONG;
if ((FLSTAT_TOP_DIR & f->st.flags))
flag |= FLIST_TOP_LEVEL;
LOG3("%s: sending file metadata: "
"size %jd, mtime %jd, mode %o",
fn, (intmax_t)f->st.size,
(intmax_t)f->st.mtime, f->st.mode);
if (!io_write_byte(sess, fdout, flag)) {
ERRX1("io_write_byte");
goto out;
} else if (!io_write_int(sess, fdout, sz)) {
ERRX1("io_write_int");
goto out;
} else if (!io_write_buf(sess, fdout, fn, sz)) {
ERRX1("io_write_buf");
goto out;
} else if (!io_write_long(sess, fdout, f->st.size)) {
ERRX1("io_write_long");
goto out;
} else if (!io_write_uint(sess, fdout, (uint32_t)f->st.mtime)) {
ERRX1("io_write_uint");
goto out;
} else if (!io_write_uint(sess, fdout, f->st.mode)) {
ERRX1("io_write_uint");
goto out;
}
if (sess->opts->preserve_uids) {
if (!io_write_uint(sess, fdout, f->st.uid)) {
ERRX1("io_write_uint");
goto out;
}
if (!idents_add(0, &uids, &uidsz, f->st.uid)) {
ERRX1("idents_add");
goto out;
}
}
if (sess->opts->preserve_gids) {
if (!io_write_uint(sess, fdout, f->st.gid)) {
ERRX1("io_write_uint");
goto out;
}
if (!idents_add(1, &gids, &gidsz, f->st.gid)) {
ERRX1("idents_add");
goto out;
}
}
if ((sess->opts->devices && (S_ISBLK(f->st.mode) ||
S_ISCHR(f->st.mode))) ||
(sess->opts->specials && (S_ISFIFO(f->st.mode) ||
S_ISSOCK(f->st.mode)))) {
if (!io_write_int(sess, fdout, f->st.rdev)) {
ERRX1("io_write_int");
goto out;
}
}
if (S_ISLNK(f->st.mode) &&
sess->opts->preserve_links) {
fn = f->link;
sz = strlen(f->link);
assert(sz < INT32_MAX);
if (!io_write_int(sess, fdout, sz)) {
ERRX1("io_write_int");
goto out;
}
if (!io_write_buf(sess, fdout, fn, sz)) {
ERRX1("io_write_buf");
goto out;
}
}
if (S_ISREG(f->st.mode))
sess->total_size += f->st.size;
}
if (!io_write_byte(sess, fdout, 0)) {
ERRX1("io_write_byte");
goto out;
}
if (sess->opts->preserve_uids && !sess->opts->numeric_ids) {
LOG2("sending uid list: %zu", uidsz);
if (!idents_send(sess, fdout, uids, uidsz)) {
ERRX1("idents_send");
goto out;
}
}
if (sess->opts->preserve_gids && !sess->opts->numeric_ids) {
LOG2("sending gid list: %zu", gidsz);
if (!idents_send(sess, fdout, gids, gidsz)) {
ERRX1("idents_send");
goto out;
}
}
rc = 1;
out:
idents_free(gids, gidsz);
idents_free(uids, uidsz);
return rc;
}
static int
flist_recv_name(struct sess *sess, int fd, struct flist *f, uint8_t flags,
char last[PATH_MAX])
{
uint8_t bval;
size_t partial = 0;
size_t pathlen = 0, len;
if (flags & FLIST_NAME_SAME) {
if (!io_read_byte(sess, fd, &bval)) {
ERRX1("io_read_byte");
return 0;
}
partial = bval;
}
if (flags & FLIST_NAME_LONG) {
if (!io_read_size(sess, fd, &pathlen)) {
ERRX1("io_read_size");
return 0;
}
} else {
if (!io_read_byte(sess, fd, &bval)) {
ERRX1("io_read_byte");
return 0;
}
pathlen = bval;
}
if ((len = pathlen + partial) == 0) {
ERRX("security violation: zero-length pathname");
return 0;
}
if ((f->path = malloc(len + 1)) == NULL) {
ERR("malloc");
return 0;
}
f->path[len] = '\0';
if (flags & FLIST_NAME_SAME)
memcpy(f->path, last, partial);
if (!io_read_buf(sess, fd, f->path + partial, pathlen)) {
ERRX1("io_read_buf");
return 0;
}
if (f->path[0] == '/') {
ERRX("security violation: absolute pathname: %s",
f->path);
return 0;
}
if (strstr(f->path, "/../") != NULL ||
(len > 2 && strcmp(f->path + len - 3, "/..") == 0) ||
(len > 2 && strncmp(f->path, "../", 3) == 0) ||
strcmp(f->path, "..") == 0) {
ERRX("%s: security violation: backtracking pathname",
f->path);
return 0;
}
strlcpy(last, f->path, PATH_MAX);
f->wpath = f->path;
return 1;
}
static int
flist_realloc(struct flist **fl, size_t *sz, size_t *max)
{
void *pp;
if (*sz + 1 <= *max) {
(*sz)++;
return 1;
}
pp = recallocarray(*fl, *max,
*max + FLIST_CHUNK_SIZE, sizeof(struct flist));
if (pp == NULL) {
ERR("recallocarray");
return 0;
}
*fl = pp;
*max += FLIST_CHUNK_SIZE;
(*sz)++;
return 1;
}
static int
flist_append(struct flist *f, struct stat *st, const char *path)
{
if ((f->path = strdup(path)) == NULL) {
ERR("strdup");
return 0;
}
if ((f->wpath = strrchr(f->path, '/')) == NULL)
f->wpath = f->path;
else
f->wpath++;
flist_copy_stat(f, st);
if (S_ISLNK(st->st_mode)) {
f->link = symlink_read(f->path);
if (f->link == NULL) {
ERRX1("symlink_read");
return 0;
}
}
return 1;
}
int
flist_recv(struct sess *sess, int fd, struct flist **flp, size_t *sz)
{
struct flist *fl = NULL;
struct flist *ff;
const struct flist *fflast = NULL;
size_t flsz = 0, flmax = 0, lsz, gidsz = 0, uidsz = 0;
uint8_t flag;
char last[PATH_MAX];
int64_t lval;
int32_t ival;
uint32_t uival;
struct ident *gids = NULL, *uids = NULL;
last[0] = '\0';
for (;;) {
if (!io_read_byte(sess, fd, &flag)) {
ERRX1("io_read_byte");
goto out;
} else if (flag == 0)
break;
if (!flist_realloc(&fl, &flsz, &flmax)) {
ERRX1("flist_realloc");
goto out;
}
ff = &fl[flsz - 1];
fflast = flsz > 1 ? &fl[flsz - 2] : NULL;
if (!flist_recv_name(sess, fd, ff, flag, last)) {
ERRX1("flist_recv_name");
goto out;
}
if (!io_read_long(sess, fd, &lval)) {
ERRX1("io_read_long");
goto out;
}
ff->st.size = lval;
if (!(flag & FLIST_TIME_SAME)) {
if (!io_read_uint(sess, fd, &uival)) {
ERRX1("io_read_uint");
goto out;
}
ff->st.mtime = uival;
} else if (fflast == NULL) {
ff->st.mtime = 0;
} else
ff->st.mtime = fflast->st.mtime;
if (!(flag & FLIST_MODE_SAME)) {
if (!io_read_uint(sess, fd, &uival)) {
ERRX1("io_read_uint");
goto out;
}
ff->st.mode = uival;
} else if (fflast == NULL) {
ff->st.mode = 0;
} else
ff->st.mode = fflast->st.mode;
if (sess->opts->preserve_uids) {
if (!(flag & FLIST_UID_SAME)) {
if (!io_read_uint(sess, fd, &uival)) {
ERRX1("io_read_int");
goto out;
}
ff->st.uid = uival;
} else if (fflast == NULL) {
ff->st.uid = 0;
} else
ff->st.uid = fflast->st.uid;
}
if (sess->opts->preserve_gids) {
if (!(flag & FLIST_GID_SAME)) {
if (!io_read_uint(sess, fd, &uival)) {
ERRX1("io_read_uint");
goto out;
}
ff->st.gid = uival;
} else if (fflast == NULL) {
ff->st.gid = 0;
} else
ff->st.gid = fflast->st.gid;
}
if ((sess->opts->devices && (S_ISBLK(ff->st.mode) ||
S_ISCHR(ff->st.mode))) ||
(sess->opts->specials && (S_ISFIFO(ff->st.mode) ||
S_ISSOCK(ff->st.mode)))) {
if (!(flag & FLIST_RDEV_SAME)) {
if (!io_read_int(sess, fd, &ival)) {
ERRX1("io_read_int");
goto out;
}
ff->st.rdev = ival;
} else if (fflast == NULL) {
ff->st.rdev = 0;
} else
ff->st.rdev = fflast->st.rdev;
}
if (S_ISLNK(ff->st.mode) &&
sess->opts->preserve_links) {
if (!io_read_size(sess, fd, &lsz)) {
ERRX1("io_read_size");
goto out;
} else if (lsz == 0) {
ERRX("empty link name");
goto out;
}
ff->link = calloc(lsz + 1, 1);
if (ff->link == NULL) {
ERR("calloc");
goto out;
}
if (!io_read_buf(sess, fd, ff->link, lsz)) {
ERRX1("io_read_buf");
goto out;
}
}
LOG3("%s: received file metadata: "
"size %jd, mtime %jd, mode %o, rdev (%d, %d)",
ff->path, (intmax_t)ff->st.size,
(intmax_t)ff->st.mtime, ff->st.mode,
major(ff->st.rdev), minor(ff->st.rdev));
if (S_ISREG(ff->st.mode))
sess->total_size += ff->st.size;
}
if (sess->opts->preserve_uids && !sess->opts->numeric_ids) {
if (!idents_recv(sess, fd, &uids, &uidsz)) {
ERRX1("idents_recv");
goto out;
}
LOG2("received uid list: %zu", uidsz);
}
if (sess->opts->preserve_gids && !sess->opts->numeric_ids) {
if (!idents_recv(sess, fd, &gids, &gidsz)) {
ERRX1("idents_recv");
goto out;
}
LOG2("received gid list: %zu", gidsz);
}
LOG2("received file metadata list: %zu", flsz);
qsort(fl, flsz, sizeof(struct flist), flist_cmp);
flist_topdirs(sess, fl, flsz);
*sz = flsz;
*flp = fl;
if (sess->opts->preserve_uids && !sess->opts->numeric_ids) {
idents_remap(sess, 0, uids, uidsz);
idents_assign_uid(sess, fl, flsz, uids, uidsz);
}
if (sess->opts->preserve_gids && !sess->opts->numeric_ids) {
idents_remap(sess, 1, gids, gidsz);
idents_assign_gid(sess, fl, flsz, gids, gidsz);
}
idents_free(gids, gidsz);
idents_free(uids, uidsz);
return 1;
out:
flist_free(fl, flsz);
idents_free(gids, gidsz);
idents_free(uids, uidsz);
*sz = 0;
*flp = NULL;
return 0;
}
static int
flist_gen_dirent(struct sess *sess, char *root, struct flist **fl, size_t *sz,
size_t *max)
{
char *cargv[2], *cp;
int rc = 0, flag;
FTS *fts;
FTSENT *ent;
struct flist *f;
size_t i, flsz = 0, nxdev = 0, stripdir;
dev_t *newxdev, *xdev = NULL;
struct stat st;
cargv[0] = root;
cargv[1] = NULL;
if (lstat(root, &st) == -1) {
ERR("%s: lstat", root);
return 0;
} else if (S_ISREG(st.st_mode)) {
if (rules_match(root, 0) == -1) {
WARNX("%s: skipping excluded file", root);
return 1;
}
if (!flist_realloc(fl, sz, max)) {
ERRX1("flist_realloc");
return 0;
}
f = &(*fl)[(*sz) - 1];
assert(f != NULL);
if (!flist_append(f, &st, root)) {
ERRX1("flist_append");
return 0;
}
return 1;
} else if (S_ISLNK(st.st_mode)) {
if (!sess->opts->preserve_links) {
WARNX("%s: skipping symlink", root);
return 1;
}
if (rules_match(root, 0) == -1) {
WARNX("%s: skipping excluded symlink", root);
return 1;
}
if (!flist_realloc(fl, sz, max)) {
ERRX1("flist_realloc");
return 0;
}
f = &(*fl)[(*sz) - 1];
assert(f != NULL);
if (!flist_append(f, &st, root)) {
ERRX1("flist_append");
return 0;
}
return 1;
} else if (!S_ISDIR(st.st_mode)) {
WARNX("%s: skipping special", root);
return 1;
}
stripdir = strlen(root);
assert(stripdir > 0);
if (root[stripdir - 1] != '/')
stripdir = 0;
if (stripdir == 0)
if ((cp = strrchr(root, '/')) != NULL)
stripdir = cp - root + 1;
if ((fts = fts_open(cargv, FTS_PHYSICAL, NULL)) == NULL) {
ERR("fts_open");
return 0;
}
errno = 0;
while ((ent = fts_read(fts)) != NULL) {
if (!flist_fts_check(sess, ent)) {
errno = 0;
continue;
}
assert(ent->fts_statp != NULL);
if (S_ISLNK(ent->fts_statp->st_mode) &&
!sess->opts->preserve_links) {
WARNX("%s: skipping symlink", ent->fts_path);
continue;
}
if (sess->opts->one_file_system &&
ent->fts_statp->st_dev != st.st_dev) {
if (sess->opts->one_file_system > 1 ||
!S_ISDIR(ent->fts_statp->st_mode))
continue;
flag = 0;
for (i = 0; i < nxdev; i++)
if (xdev[i] == ent->fts_statp->st_dev) {
flag = 1;
break;
}
if (flag)
continue;
if ((newxdev = reallocarray(xdev, nxdev + 1,
sizeof(dev_t))) == NULL) {
ERRX1("reallocarray");
goto out;
}
xdev = newxdev;
xdev[nxdev] = ent->fts_statp->st_dev;
nxdev++;
}
if (rules_match(ent->fts_path + stripdir,
(ent->fts_info == FTS_D)) == -1) {
WARNX("%s: skipping excluded file",
ent->fts_path + stripdir);
fts_set(fts, ent, FTS_SKIP);
continue;
}
if (!flist_realloc(fl, sz, max)) {
ERRX1("flist_realloc");
goto out;
}
flsz++;
f = &(*fl)[*sz - 1];
if (ent->fts_path[stripdir] == '\0') {
if (asprintf(&f->path, "%s.", ent->fts_path) == -1) {
ERR("asprintf");
f->path = NULL;
goto out;
}
} else {
if ((f->path = strdup(ent->fts_path)) == NULL) {
ERR("strdup");
goto out;
}
}
f->wpath = f->path + stripdir;
flist_copy_stat(f, ent->fts_statp);
if (S_ISLNK(ent->fts_statp->st_mode)) {
f->link = symlink_read(ent->fts_accpath);
if (f->link == NULL) {
ERRX1("symlink_read");
goto out;
}
}
errno = 0;
}
if (errno) {
ERR("fts_read");
goto out;
}
LOG3("generated %zu filenames: %s", flsz, root);
rc = 1;
out:
fts_close(fts);
free(xdev);
return rc;
}
static int
flist_gen_dirs(struct sess *sess, size_t argc, char **argv, struct flist **flp,
size_t *sz)
{
size_t i, max = 0;
for (i = 0; i < argc; i++)
if (!flist_gen_dirent(sess, argv[i], flp, sz, &max))
break;
if (i == argc) {
LOG2("recursively generated %zu filenames", *sz);
return 1;
}
ERRX1("flist_gen_dirent");
flist_free(*flp, max);
*flp = NULL;
*sz = 0;
return 0;
}
static int
flist_gen_files(struct sess *sess, size_t argc, char **argv,
struct flist **flp, size_t *sz)
{
struct flist *fl = NULL, *f;
size_t i, flsz = 0;
struct stat st;
assert(argc);
if ((fl = calloc(argc, sizeof(struct flist))) == NULL) {
ERR("calloc");
return 0;
}
for (i = 0; i < argc; i++) {
if (argv[i][0] == '\0')
continue;
if (lstat(argv[i], &st) == -1) {
ERR("%s: lstat", argv[i]);
goto out;
}
if (S_ISDIR(st.st_mode)) {
WARNX("%s: skipping directory", argv[i]);
continue;
} else if (S_ISLNK(st.st_mode)) {
if (!sess->opts->preserve_links) {
WARNX("%s: skipping symlink", argv[i]);
continue;
}
} else if (!S_ISREG(st.st_mode)) {
WARNX("%s: skipping special", argv[i]);
continue;
}
if (rules_match(argv[i], S_ISDIR(st.st_mode)) == -1) {
WARNX("%s: skipping excluded file", argv[i]);
continue;
}
f = &fl[flsz++];
assert(f != NULL);
if (!flist_append(f, &st, argv[i])) {
ERRX1("flist_append");
goto out;
}
}
LOG2("non-recursively generated %zu filenames", flsz);
*sz = flsz;
*flp = fl;
return 1;
out:
flist_free(fl, argc);
*sz = 0;
*flp = NULL;
return 0;
}
int
flist_gen(struct sess *sess, size_t argc, char **argv, struct flist **flp,
size_t *sz)
{
int rc;
assert(argc > 0);
rc = sess->opts->recursive ?
flist_gen_dirs(sess, argc, argv, flp, sz) :
flist_gen_files(sess, argc, argv, flp, sz);
if (!rc)
return 0;
qsort(*flp, *sz, sizeof(struct flist), flist_cmp);
if (flist_dedupe(flp, sz)) {
flist_topdirs(sess, *flp, *sz);
return 1;
}
ERRX1("flist_dedupe");
flist_free(*flp, *sz);
*flp = NULL;
*sz = 0;
return 0;
}
int
flist_gen_dels(struct sess *sess, const char *root, struct flist **fl,
size_t *sz, const struct flist *wfl, size_t wflsz)
{
char **cargv = NULL;
int rc = 0, c, flag;
FTS *fts = NULL;
FTSENT *ent;
struct flist *f;
struct stat st;
size_t cargvs = 0, i, j, max = 0, stripdir;
ENTRY hent;
ENTRY *hentp;
*fl = NULL;
*sz = 0;
if (!sess->opts->recursive)
return 1;
assert(wflsz > 0);
for (i = 0; i < wflsz; i++)
if (FLSTAT_TOP_DIR & wfl[i].st.flags)
cargvs++;
if (cargvs == 0)
return 1;
if ((cargv = calloc(cargvs + 1, sizeof(char *))) == NULL) {
ERR("calloc");
return 0;
}
if (wflsz && strcmp(wfl[0].wpath, ".") == 0) {
assert(cargvs == 1);
assert(S_ISDIR(wfl[0].st.mode));
if (asprintf(&cargv[0], "%s/", root) == -1) {
ERR("asprintf");
cargv[0] = NULL;
goto out;
}
cargv[1] = NULL;
} else {
for (i = j = 0; i < wflsz; i++) {
if (!(FLSTAT_TOP_DIR & wfl[i].st.flags))
continue;
assert(S_ISDIR(wfl[i].st.mode));
assert(strcmp(wfl[i].wpath, "."));
c = asprintf(&cargv[j], "%s/%s", root, wfl[i].wpath);
if (c == -1) {
ERR("asprintf");
cargv[j] = NULL;
goto out;
}
LOG4("%s: will scan for deletions", cargv[j]);
j++;
}
assert(j == cargvs);
cargv[j] = NULL;
}
LOG2("delete from %zu directories", cargvs);
if (!hcreate(wflsz)) {
ERR("hcreate");
goto out;
}
for (i = 0; i < wflsz; i++) {
memset(&hent, 0, sizeof(ENTRY));
if ((hent.key = strdup(wfl[i].wpath)) == NULL) {
ERR("strdup");
goto out;
}
if ((hentp = hsearch(hent, ENTER)) == NULL) {
ERR("hsearch");
goto out;
} else if (hentp->key != hent.key) {
ERRX("%s: duplicate", wfl[i].wpath);
free(hent.key);
goto out;
}
}
if ((fts = fts_open(cargv, FTS_PHYSICAL, NULL)) == NULL) {
ERR("fts_open");
goto out;
}
stripdir = strlen(root) + 1;
errno = 0;
while ((ent = fts_read(fts)) != NULL) {
if (ent->fts_info == FTS_NS)
continue;
if (!flist_fts_check(sess, ent)) {
errno = 0;
continue;
} else if (stripdir >= ent->fts_pathlen)
continue;
assert(ent->fts_statp != NULL);
if (sess->opts->one_file_system) {
flag = 0;
for (i = 0; i < wflsz; i++) {
if (stat(wfl[i].path, &st) == -1) {
ERR("%s: stat", wfl[i].path);
goto out;
}
if (ent->fts_statp->st_dev == st.st_dev) {
flag = 1;
break;
}
}
if (!flag)
continue;
}
if (rules_match(ent->fts_path + stripdir,
(ent->fts_info == FTS_D)) == -1) {
WARNX("skip excluded file %s",
ent->fts_path + stripdir);
fts_set(fts, ent, FTS_SKIP);
continue;
}
memset(&hent, 0, sizeof(ENTRY));
hent.key = ent->fts_path + stripdir;
if (hsearch(hent, FIND) != NULL)
continue;
if (!flist_realloc(fl, sz, &max)) {
ERRX1("flist_realloc");
goto out;
}
f = &(*fl)[*sz - 1];
if ((f->path = strdup(ent->fts_path)) == NULL) {
ERR("strdup");
goto out;
}
f->wpath = f->path + stripdir;
flist_copy_stat(f, ent->fts_statp);
errno = 0;
}
if (errno) {
ERR("fts_read");
goto out;
}
qsort(*fl, *sz, sizeof(struct flist), flist_cmp);
rc = 1;
out:
if (fts != NULL)
fts_close(fts);
for (i = 0; i < cargvs; i++)
free(cargv[i]);
free(cargv);
hdestroy();
return rc;
}
int
flist_del(struct sess *sess, int root, const struct flist *fl, size_t flsz)
{
ssize_t i;
int flag;
if (flsz == 0)
return 1;
assert(sess->opts->del);
assert(sess->opts->recursive);
for (i = flsz - 1; i >= 0; i--) {
LOG1("%s: deleting", fl[i].wpath);
if (sess->opts->dry_run)
continue;
assert(root != -1);
flag = S_ISDIR(fl[i].st.mode) ? AT_REMOVEDIR : 0;
if (unlinkat(root, fl[i].wpath, flag) == -1 &&
errno != ENOENT) {
ERR("%s: unlinkat", fl[i].wpath);
return 0;
}
}
return 1;
}