1*5fe4a96fSclaudio /* $OpenBSD: flist.c,v 1.38 2023/12/27 17:22:25 claudio Exp $ */
260a32ee9Sbenno /*
360a32ee9Sbenno * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
4dbc83512Sflorian * Copyright (c) 2019 Florian Obser <florian@openbsd.org>
560a32ee9Sbenno *
660a32ee9Sbenno * Permission to use, copy, modify, and distribute this software for any
760a32ee9Sbenno * purpose with or without fee is hereby granted, provided that the above
860a32ee9Sbenno * copyright notice and this permission notice appear in all copies.
960a32ee9Sbenno *
1060a32ee9Sbenno * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1160a32ee9Sbenno * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1260a32ee9Sbenno * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1360a32ee9Sbenno * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1460a32ee9Sbenno * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1560a32ee9Sbenno * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1660a32ee9Sbenno * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1760a32ee9Sbenno */
1860a32ee9Sbenno #include <sys/stat.h>
1960a32ee9Sbenno
2060a32ee9Sbenno #include <assert.h>
2160a32ee9Sbenno #include <errno.h>
2260a32ee9Sbenno #include <fcntl.h>
2360a32ee9Sbenno #include <fts.h>
24173dc874Sderaadt #include <limits.h>
256a02c679Sbenno #include <inttypes.h>
2660a32ee9Sbenno #include <search.h>
2760a32ee9Sbenno #include <stdio.h>
2860a32ee9Sbenno #include <stdlib.h>
2960a32ee9Sbenno #include <string.h>
3060a32ee9Sbenno #include <unistd.h>
3160a32ee9Sbenno
3260a32ee9Sbenno #include "extern.h"
3360a32ee9Sbenno
3460a32ee9Sbenno /*
3560a32ee9Sbenno * We allocate our file list in chunk sizes so as not to do it one by
3660a32ee9Sbenno * one.
37d9a51c35Sjmc * Preferably we get one or two allocation.
3860a32ee9Sbenno */
3960a32ee9Sbenno #define FLIST_CHUNK_SIZE (1024)
4060a32ee9Sbenno
4160a32ee9Sbenno /*
4260a32ee9Sbenno * These flags are part of the rsync protocol.
4360a32ee9Sbenno * They are sent as the first byte for a file transmission and encode
4460a32ee9Sbenno * information that affects subsequent transmissions.
4560a32ee9Sbenno */
46521a61e9Sbenno #define FLIST_TOP_LEVEL 0x0001 /* needed for remote --delete */
4760a32ee9Sbenno #define FLIST_MODE_SAME 0x0002 /* mode is repeat */
48434f41cdSflorian #define FLIST_RDEV_SAME 0x0004 /* rdev is repeat */
498f34fbc5Sflorian #define FLIST_UID_SAME 0x0008 /* uid is repeat */
50ea2cd662Sbenno #define FLIST_GID_SAME 0x0010 /* gid is repeat */
5160a32ee9Sbenno #define FLIST_NAME_SAME 0x0020 /* name is repeat */
5260a32ee9Sbenno #define FLIST_NAME_LONG 0x0040 /* name >255 bytes */
5360a32ee9Sbenno #define FLIST_TIME_SAME 0x0080 /* time is repeat */
5460a32ee9Sbenno
5560a32ee9Sbenno /*
5669cdce33Sderaadt * Required way to sort a filename list.
5760a32ee9Sbenno */
5860a32ee9Sbenno static int
flist_cmp(const void * p1,const void * p2)5960a32ee9Sbenno flist_cmp(const void *p1, const void *p2)
6060a32ee9Sbenno {
6160a32ee9Sbenno const struct flist *f1 = p1, *f2 = p2;
6260a32ee9Sbenno
6360a32ee9Sbenno return strcmp(f1->wpath, f2->wpath);
6460a32ee9Sbenno }
6560a32ee9Sbenno
6660a32ee9Sbenno /*
6760a32ee9Sbenno * Deduplicate our file list (which may be zero-length).
6860a32ee9Sbenno * Returns zero on failure, non-zero on success.
6960a32ee9Sbenno */
7060a32ee9Sbenno static int
flist_dedupe(struct flist ** fl,size_t * sz)71ba617adaSbenno flist_dedupe(struct flist **fl, size_t *sz)
7260a32ee9Sbenno {
7360a32ee9Sbenno size_t i, j;
7460a32ee9Sbenno struct flist *new;
7560a32ee9Sbenno struct flist *f, *fnext;
7660a32ee9Sbenno
77f1dcb30aSderaadt if (*sz == 0)
7860a32ee9Sbenno return 1;
7960a32ee9Sbenno
8060a32ee9Sbenno /* Create a new buffer, "new", and copy. */
8160a32ee9Sbenno
8260a32ee9Sbenno new = calloc(*sz, sizeof(struct flist));
83f1dcb30aSderaadt if (new == NULL) {
84b2a7eac7Sbenno ERR("calloc");
8560a32ee9Sbenno return 0;
8660a32ee9Sbenno }
8760a32ee9Sbenno
8860a32ee9Sbenno for (i = j = 0; i < *sz - 1; i++) {
8960a32ee9Sbenno f = &(*fl)[i];
9060a32ee9Sbenno fnext = &(*fl)[i + 1];
9160a32ee9Sbenno
9260a32ee9Sbenno if (strcmp(f->wpath, fnext->wpath)) {
9360a32ee9Sbenno new[j++] = *f;
9460a32ee9Sbenno continue;
9560a32ee9Sbenno }
9660a32ee9Sbenno
9760a32ee9Sbenno /*
9860a32ee9Sbenno * Our working (destination) paths are the same.
9960a32ee9Sbenno * If the actual file is the same (as given on the
10060a32ee9Sbenno * command-line), then we can just discard the first.
10160a32ee9Sbenno * Otherwise, we need to bail out: it means we have two
10260a32ee9Sbenno * different files with the relative path on the
10360a32ee9Sbenno * destination side.
10460a32ee9Sbenno */
10560a32ee9Sbenno
106f1dcb30aSderaadt if (strcmp(f->path, fnext->path) == 0) {
10760a32ee9Sbenno new[j++] = *f;
10860a32ee9Sbenno i++;
109b2a7eac7Sbenno WARNX("%s: duplicate path: %s",
11060a32ee9Sbenno f->wpath, f->path);
11160a32ee9Sbenno free(fnext->path);
11260a32ee9Sbenno free(fnext->link);
11360a32ee9Sbenno fnext->path = fnext->link = NULL;
11460a32ee9Sbenno continue;
11560a32ee9Sbenno }
11660a32ee9Sbenno
117b2a7eac7Sbenno ERRX("%s: duplicate working path for "
11860a32ee9Sbenno "possibly different file: %s, %s",
11960a32ee9Sbenno f->wpath, f->path, fnext->path);
12060a32ee9Sbenno free(new);
12160a32ee9Sbenno return 0;
12260a32ee9Sbenno }
12360a32ee9Sbenno
12460a32ee9Sbenno /* Don't forget the last entry. */
12560a32ee9Sbenno
12660a32ee9Sbenno if (i == *sz - 1)
12760a32ee9Sbenno new[j++] = (*fl)[i];
12860a32ee9Sbenno
12960a32ee9Sbenno /*
13060a32ee9Sbenno * Reassign to the deduplicated array.
13160a32ee9Sbenno * If we started out with *sz > 0, which we check for at the
13260a32ee9Sbenno * beginning, then we'll always continue having *sz > 0.
13360a32ee9Sbenno */
13460a32ee9Sbenno
13560a32ee9Sbenno free(*fl);
13660a32ee9Sbenno *fl = new;
13760a32ee9Sbenno *sz = j;
13860a32ee9Sbenno assert(*sz);
13960a32ee9Sbenno return 1;
14060a32ee9Sbenno }
14160a32ee9Sbenno
14260a32ee9Sbenno /*
14360a32ee9Sbenno * We're now going to find our top-level directories.
14460a32ee9Sbenno * This only applies to recursive mode.
14560a32ee9Sbenno * If we have the first element as the ".", then that's the "top
14660a32ee9Sbenno * directory" of our transfer.
14760a32ee9Sbenno * Otherwise, mark up all top-level directories in the set.
148521a61e9Sbenno * XXX: the FLIST_TOP_LEVEL flag should indicate what is and what isn't
149521a61e9Sbenno * a top-level directory, but I'm not sure if GPL rsync(1) respects it
150521a61e9Sbenno * the same way.
15160a32ee9Sbenno */
15260a32ee9Sbenno static void
flist_topdirs(struct sess * sess,struct flist * fl,size_t flsz)15360a32ee9Sbenno flist_topdirs(struct sess *sess, struct flist *fl, size_t flsz)
15460a32ee9Sbenno {
15560a32ee9Sbenno size_t i;
15660a32ee9Sbenno const char *cp;
15760a32ee9Sbenno
15860a32ee9Sbenno if (!sess->opts->recursive)
15960a32ee9Sbenno return;
16060a32ee9Sbenno
16160a32ee9Sbenno if (flsz && strcmp(fl[0].wpath, ".")) {
16260a32ee9Sbenno for (i = 0; i < flsz; i++) {
16360a32ee9Sbenno if (!S_ISDIR(fl[i].st.mode))
16460a32ee9Sbenno continue;
16560a32ee9Sbenno cp = strchr(fl[i].wpath, '/');
166f1dcb30aSderaadt if (cp != NULL && cp[1] != '\0')
16760a32ee9Sbenno continue;
16860a32ee9Sbenno fl[i].st.flags |= FLSTAT_TOP_DIR;
169b2a7eac7Sbenno LOG4("%s: top-level", fl[i].wpath);
17060a32ee9Sbenno }
17160a32ee9Sbenno } else if (flsz) {
17260a32ee9Sbenno fl[0].st.flags |= FLSTAT_TOP_DIR;
173b2a7eac7Sbenno LOG4("%s: top-level", fl[0].wpath);
17460a32ee9Sbenno }
17560a32ee9Sbenno }
17660a32ee9Sbenno
17760a32ee9Sbenno /*
17860a32ee9Sbenno * Filter through the fts() file information.
17960a32ee9Sbenno * We want directories (pre-order), regular files, and symlinks.
18060a32ee9Sbenno * Everything else is skipped and possibly warned about.
18160a32ee9Sbenno * Return zero to skip, non-zero to examine.
18260a32ee9Sbenno */
18360a32ee9Sbenno static int
flist_fts_check(struct sess * sess,FTSENT * ent)18460a32ee9Sbenno flist_fts_check(struct sess *sess, FTSENT *ent)
18560a32ee9Sbenno {
18660a32ee9Sbenno
187f1dcb30aSderaadt if (ent->fts_info == FTS_F ||
188f1dcb30aSderaadt ent->fts_info == FTS_D ||
189f1dcb30aSderaadt ent->fts_info == FTS_SL ||
190f1dcb30aSderaadt ent->fts_info == FTS_SLNONE)
19160a32ee9Sbenno return 1;
19260a32ee9Sbenno
193f1dcb30aSderaadt if (ent->fts_info == FTS_DC) {
194b2a7eac7Sbenno WARNX("%s: directory cycle", ent->fts_path);
195f1dcb30aSderaadt } else if (ent->fts_info == FTS_DNR) {
19660a32ee9Sbenno errno = ent->fts_errno;
197b2a7eac7Sbenno WARN("%s: unreadable directory", ent->fts_path);
198f1dcb30aSderaadt } else if (ent->fts_info == FTS_DOT) {
199b2a7eac7Sbenno WARNX("%s: skipping dot-file", ent->fts_path);
200f1dcb30aSderaadt } else if (ent->fts_info == FTS_ERR) {
20160a32ee9Sbenno errno = ent->fts_errno;
202b2a7eac7Sbenno WARN("%s", ent->fts_path);
203f1dcb30aSderaadt } else if (ent->fts_info == FTS_DEFAULT) {
204434f41cdSflorian if ((sess->opts->devices && (S_ISBLK(ent->fts_statp->st_mode) ||
205434f41cdSflorian S_ISCHR(ent->fts_statp->st_mode))) ||
206434f41cdSflorian (sess->opts->specials &&
207434f41cdSflorian (S_ISFIFO(ent->fts_statp->st_mode) ||
208434f41cdSflorian S_ISSOCK(ent->fts_statp->st_mode)))) {
209434f41cdSflorian return 1;
210434f41cdSflorian }
211b2a7eac7Sbenno WARNX("%s: skipping special", ent->fts_path);
212f1dcb30aSderaadt } else if (ent->fts_info == FTS_NS) {
21360a32ee9Sbenno errno = ent->fts_errno;
214b2a7eac7Sbenno WARN("%s: could not stat", ent->fts_path);
21560a32ee9Sbenno }
21660a32ee9Sbenno
21760a32ee9Sbenno return 0;
21860a32ee9Sbenno }
21960a32ee9Sbenno
22060a32ee9Sbenno /*
22160a32ee9Sbenno * Copy necessary elements in "st" into the fields of "f".
22260a32ee9Sbenno */
22360a32ee9Sbenno static void
flist_copy_stat(struct flist * f,const struct stat * st)22460a32ee9Sbenno flist_copy_stat(struct flist *f, const struct stat *st)
22560a32ee9Sbenno {
22660a32ee9Sbenno f->st.mode = st->st_mode;
22760a32ee9Sbenno f->st.uid = st->st_uid;
22860a32ee9Sbenno f->st.gid = st->st_gid;
22960a32ee9Sbenno f->st.size = st->st_size;
23060a32ee9Sbenno f->st.mtime = st->st_mtime;
231434f41cdSflorian f->st.rdev = st->st_rdev;
23260a32ee9Sbenno }
23360a32ee9Sbenno
23460a32ee9Sbenno void
flist_free(struct flist * f,size_t sz)23560a32ee9Sbenno flist_free(struct flist *f, size_t sz)
23660a32ee9Sbenno {
23760a32ee9Sbenno size_t i;
23860a32ee9Sbenno
239f1dcb30aSderaadt if (f == NULL)
24060a32ee9Sbenno return;
24160a32ee9Sbenno
24260a32ee9Sbenno for (i = 0; i < sz; i++) {
24360a32ee9Sbenno free(f[i].path);
24460a32ee9Sbenno free(f[i].link);
24560a32ee9Sbenno }
24660a32ee9Sbenno free(f);
24760a32ee9Sbenno }
24860a32ee9Sbenno
24960a32ee9Sbenno /*
25060a32ee9Sbenno * Serialise our file list (which may be zero-length) to the wire.
25160a32ee9Sbenno * Makes sure that the receiver isn't going to block on sending us
25260a32ee9Sbenno * return messages on the log channel.
25360a32ee9Sbenno * Return zero on failure, non-zero on success.
25460a32ee9Sbenno */
25560a32ee9Sbenno int
flist_send(struct sess * sess,int fdin,int fdout,const struct flist * fl,size_t flsz)256df3fb795Sbenno flist_send(struct sess *sess, int fdin, int fdout, const struct flist *fl,
257df3fb795Sbenno size_t flsz)
25860a32ee9Sbenno {
2598f34fbc5Sflorian size_t i, sz, gidsz = 0, uidsz = 0;
26060a32ee9Sbenno uint8_t flag;
26160a32ee9Sbenno const struct flist *f;
26260a32ee9Sbenno const char *fn;
2638f34fbc5Sflorian struct ident *gids = NULL, *uids = NULL;
2646a02c679Sbenno int rc = 0;
26560a32ee9Sbenno
26660a32ee9Sbenno /* Double-check that we've no pending multiplexed data. */
26760a32ee9Sbenno
268b2a7eac7Sbenno LOG2("sending file metadata list: %zu", flsz);
26960a32ee9Sbenno
27060a32ee9Sbenno for (i = 0; i < flsz; i++) {
27160a32ee9Sbenno f = &fl[i];
27260a32ee9Sbenno fn = f->wpath;
2736a02c679Sbenno sz = strlen(f->wpath);
2746a02c679Sbenno assert(sz > 0);
275aa1dcd86Sderaadt assert(sz < INT32_MAX);
27660a32ee9Sbenno
27760a32ee9Sbenno /*
27860a32ee9Sbenno * If applicable, unclog the read buffer.
27960a32ee9Sbenno * This happens when the receiver has a lot of log
28060a32ee9Sbenno * messages and all we're doing is sending our file list
28160a32ee9Sbenno * without checking for messages.
28260a32ee9Sbenno */
28360a32ee9Sbenno
28460a32ee9Sbenno if (sess->mplex_reads &&
285ba617adaSbenno io_read_check(fdin) &&
28660a32ee9Sbenno !io_read_flush(sess, fdin)) {
287b2a7eac7Sbenno ERRX1("io_read_flush");
2886a02c679Sbenno goto out;
28960a32ee9Sbenno }
29060a32ee9Sbenno
29160a32ee9Sbenno /*
29260a32ee9Sbenno * For ease, make all of our filenames be "long"
29360a32ee9Sbenno * regardless their actual length.
29460a32ee9Sbenno * This also makes sure that we don't transmit a zero
29560a32ee9Sbenno * byte unintentionally.
29660a32ee9Sbenno */
29760a32ee9Sbenno
29860a32ee9Sbenno flag = FLIST_NAME_LONG;
299521a61e9Sbenno if ((FLSTAT_TOP_DIR & f->st.flags))
300521a61e9Sbenno flag |= FLIST_TOP_LEVEL;
30160a32ee9Sbenno
302b2a7eac7Sbenno LOG3("%s: sending file metadata: "
30360a32ee9Sbenno "size %jd, mtime %jd, mode %o",
30460a32ee9Sbenno fn, (intmax_t)f->st.size,
30560a32ee9Sbenno (intmax_t)f->st.mtime, f->st.mode);
30660a32ee9Sbenno
30760a32ee9Sbenno /* Now write to the wire. */
30860a32ee9Sbenno /* FIXME: buffer this. */
30960a32ee9Sbenno
31060a32ee9Sbenno if (!io_write_byte(sess, fdout, flag)) {
311b2a7eac7Sbenno ERRX1("io_write_byte");
3126a02c679Sbenno goto out;
3136a02c679Sbenno } else if (!io_write_int(sess, fdout, sz)) {
314b2a7eac7Sbenno ERRX1("io_write_int");
3156a02c679Sbenno goto out;
3166a02c679Sbenno } else if (!io_write_buf(sess, fdout, fn, sz)) {
317b2a7eac7Sbenno ERRX1("io_write_buf");
3186a02c679Sbenno goto out;
31960a32ee9Sbenno } else if (!io_write_long(sess, fdout, f->st.size)) {
320b2a7eac7Sbenno ERRX1("io_write_long");
3216a02c679Sbenno goto out;
322aa1dcd86Sderaadt } else if (!io_write_uint(sess, fdout, (uint32_t)f->st.mtime)) {
323b2a7eac7Sbenno ERRX1("io_write_uint");
3246a02c679Sbenno goto out;
325aa1dcd86Sderaadt } else if (!io_write_uint(sess, fdout, f->st.mode)) {
326b2a7eac7Sbenno ERRX1("io_write_uint");
3276a02c679Sbenno goto out;
32860a32ee9Sbenno }
32960a32ee9Sbenno
3308f34fbc5Sflorian /* Conditional part: uid. */
3318f34fbc5Sflorian
3328f34fbc5Sflorian if (sess->opts->preserve_uids) {
333aa1dcd86Sderaadt if (!io_write_uint(sess, fdout, f->st.uid)) {
334b2a7eac7Sbenno ERRX1("io_write_uint");
3358f34fbc5Sflorian goto out;
3368f34fbc5Sflorian }
337ba617adaSbenno if (!idents_add(0, &uids, &uidsz, f->st.uid)) {
338b2a7eac7Sbenno ERRX1("idents_add");
3398f34fbc5Sflorian goto out;
3408f34fbc5Sflorian }
3418f34fbc5Sflorian }
3428f34fbc5Sflorian
343ea2cd662Sbenno /* Conditional part: gid. */
344ea2cd662Sbenno
3456a02c679Sbenno if (sess->opts->preserve_gids) {
346aa1dcd86Sderaadt if (!io_write_uint(sess, fdout, f->st.gid)) {
347b2a7eac7Sbenno ERRX1("io_write_uint");
3486a02c679Sbenno goto out;
3496a02c679Sbenno }
350ba617adaSbenno if (!idents_add(1, &gids, &gidsz, f->st.gid)) {
351b2a7eac7Sbenno ERRX1("idents_add");
3526a02c679Sbenno goto out;
3536a02c679Sbenno }
354ea2cd662Sbenno }
355ea2cd662Sbenno
35680ee851fSflorian /* Conditional part: devices & special files. */
35780ee851fSflorian
35880ee851fSflorian if ((sess->opts->devices && (S_ISBLK(f->st.mode) ||
35980ee851fSflorian S_ISCHR(f->st.mode))) ||
36080ee851fSflorian (sess->opts->specials && (S_ISFIFO(f->st.mode) ||
36180ee851fSflorian S_ISSOCK(f->st.mode)))) {
36280ee851fSflorian if (!io_write_int(sess, fdout, f->st.rdev)) {
363b2a7eac7Sbenno ERRX1("io_write_int");
36480ee851fSflorian goto out;
36580ee851fSflorian }
36680ee851fSflorian }
36780ee851fSflorian
368ea2cd662Sbenno /* Conditional part: link. */
36960a32ee9Sbenno
37060a32ee9Sbenno if (S_ISLNK(f->st.mode) &&
37160a32ee9Sbenno sess->opts->preserve_links) {
37260a32ee9Sbenno fn = f->link;
3736a02c679Sbenno sz = strlen(f->link);
374aa1dcd86Sderaadt assert(sz < INT32_MAX);
3756a02c679Sbenno if (!io_write_int(sess, fdout, sz)) {
376b2a7eac7Sbenno ERRX1("io_write_int");
3776a02c679Sbenno goto out;
37860a32ee9Sbenno }
3796a02c679Sbenno if (!io_write_buf(sess, fdout, fn, sz)) {
380b2a7eac7Sbenno ERRX1("io_write_buf");
3816a02c679Sbenno goto out;
38260a32ee9Sbenno }
38360a32ee9Sbenno }
38460a32ee9Sbenno
38560a32ee9Sbenno if (S_ISREG(f->st.mode))
38660a32ee9Sbenno sess->total_size += f->st.size;
38760a32ee9Sbenno }
38860a32ee9Sbenno
3896a02c679Sbenno /* Signal end of file list. */
3906a02c679Sbenno
39160a32ee9Sbenno if (!io_write_byte(sess, fdout, 0)) {
392b2a7eac7Sbenno ERRX1("io_write_byte");
3936a02c679Sbenno goto out;
39460a32ee9Sbenno }
39560a32ee9Sbenno
3968f34fbc5Sflorian /* Conditionally write identifier lists. */
3978f34fbc5Sflorian
39844dad8d1Sbenno if (sess->opts->preserve_uids && !sess->opts->numeric_ids) {
399b2a7eac7Sbenno LOG2("sending uid list: %zu", uidsz);
4008f34fbc5Sflorian if (!idents_send(sess, fdout, uids, uidsz)) {
401b2a7eac7Sbenno ERRX1("idents_send");
4028f34fbc5Sflorian goto out;
4038f34fbc5Sflorian }
4048f34fbc5Sflorian }
4056a02c679Sbenno
40644dad8d1Sbenno if (sess->opts->preserve_gids && !sess->opts->numeric_ids) {
407b2a7eac7Sbenno LOG2("sending gid list: %zu", gidsz);
408997d99f9Sbenno if (!idents_send(sess, fdout, gids, gidsz)) {
409b2a7eac7Sbenno ERRX1("idents_send");
4106a02c679Sbenno goto out;
4116a02c679Sbenno }
4126a02c679Sbenno }
4136a02c679Sbenno
4146a02c679Sbenno rc = 1;
4156a02c679Sbenno out:
416997d99f9Sbenno idents_free(gids, gidsz);
4178f34fbc5Sflorian idents_free(uids, uidsz);
4186a02c679Sbenno return rc;
41960a32ee9Sbenno }
42060a32ee9Sbenno
42160a32ee9Sbenno /*
42260a32ee9Sbenno * Read the filename of a file list.
42360a32ee9Sbenno * This is the most expensive part of the file list transfer, so a lot
42460a32ee9Sbenno * of attention has gone into transmitting as little as possible.
42560a32ee9Sbenno * Micro-optimisation, but whatever.
42660a32ee9Sbenno * Fills in "f" with the full path on success.
42760a32ee9Sbenno * Returns zero on failure, non-zero on success.
42860a32ee9Sbenno */
42960a32ee9Sbenno static int
flist_recv_name(struct sess * sess,int fd,struct flist * f,uint8_t flags,char last[PATH_MAX])430df3fb795Sbenno flist_recv_name(struct sess *sess, int fd, struct flist *f, uint8_t flags,
431173dc874Sderaadt char last[PATH_MAX])
43260a32ee9Sbenno {
43360a32ee9Sbenno uint8_t bval;
43460a32ee9Sbenno size_t partial = 0;
43560a32ee9Sbenno size_t pathlen = 0, len;
43660a32ee9Sbenno
43760a32ee9Sbenno /*
43860a32ee9Sbenno * Read our filename.
43960a32ee9Sbenno * If we have FLIST_NAME_SAME, we inherit some of the last
44060a32ee9Sbenno * transmitted name.
44160a32ee9Sbenno * If we have FLIST_NAME_LONG, then the string length is greater
44260a32ee9Sbenno * than byte-size.
44360a32ee9Sbenno */
44460a32ee9Sbenno
445*5fe4a96fSclaudio if (flags & FLIST_NAME_SAME) {
44660a32ee9Sbenno if (!io_read_byte(sess, fd, &bval)) {
447b2a7eac7Sbenno ERRX1("io_read_byte");
44860a32ee9Sbenno return 0;
44960a32ee9Sbenno }
45060a32ee9Sbenno partial = bval;
45160a32ee9Sbenno }
45260a32ee9Sbenno
45360a32ee9Sbenno /* Get the (possibly-remaining) filename length. */
45460a32ee9Sbenno
455*5fe4a96fSclaudio if (flags & FLIST_NAME_LONG) {
45660a32ee9Sbenno if (!io_read_size(sess, fd, &pathlen)) {
457b2a7eac7Sbenno ERRX1("io_read_size");
45860a32ee9Sbenno return 0;
45960a32ee9Sbenno }
46060a32ee9Sbenno } else {
46160a32ee9Sbenno if (!io_read_byte(sess, fd, &bval)) {
462b2a7eac7Sbenno ERRX1("io_read_byte");
46360a32ee9Sbenno return 0;
46460a32ee9Sbenno }
46560a32ee9Sbenno pathlen = bval;
46660a32ee9Sbenno }
46760a32ee9Sbenno
46860a32ee9Sbenno /* Allocate our full filename length. */
46960a32ee9Sbenno /* FIXME: maximum pathname length. */
47060a32ee9Sbenno
471f1dcb30aSderaadt if ((len = pathlen + partial) == 0) {
472b2a7eac7Sbenno ERRX("security violation: zero-length pathname");
47360a32ee9Sbenno return 0;
47460a32ee9Sbenno }
47560a32ee9Sbenno
476f1dcb30aSderaadt if ((f->path = malloc(len + 1)) == NULL) {
477b2a7eac7Sbenno ERR("malloc");
47860a32ee9Sbenno return 0;
47960a32ee9Sbenno }
48060a32ee9Sbenno f->path[len] = '\0';
48160a32ee9Sbenno
482*5fe4a96fSclaudio if (flags & FLIST_NAME_SAME)
48360a32ee9Sbenno memcpy(f->path, last, partial);
48460a32ee9Sbenno
48560a32ee9Sbenno if (!io_read_buf(sess, fd, f->path + partial, pathlen)) {
486b2a7eac7Sbenno ERRX1("io_read_buf");
48760a32ee9Sbenno return 0;
48860a32ee9Sbenno }
48960a32ee9Sbenno
490f1dcb30aSderaadt if (f->path[0] == '/') {
491b2a7eac7Sbenno ERRX("security violation: absolute pathname: %s",
49202f20df6Sderaadt f->path);
49360a32ee9Sbenno return 0;
49460a32ee9Sbenno }
49560a32ee9Sbenno
496f1dcb30aSderaadt if (strstr(f->path, "/../") != NULL ||
497f1dcb30aSderaadt (len > 2 && strcmp(f->path + len - 3, "/..") == 0) ||
498f1dcb30aSderaadt (len > 2 && strncmp(f->path, "../", 3) == 0) ||
499f1dcb30aSderaadt strcmp(f->path, "..") == 0) {
500b2a7eac7Sbenno ERRX("%s: security violation: backtracking pathname",
50102f20df6Sderaadt f->path);
50260a32ee9Sbenno return 0;
50360a32ee9Sbenno }
50460a32ee9Sbenno
50560a32ee9Sbenno /* Record our last path and construct our filename. */
50660a32ee9Sbenno
507173dc874Sderaadt strlcpy(last, f->path, PATH_MAX);
50860a32ee9Sbenno f->wpath = f->path;
50960a32ee9Sbenno return 1;
51060a32ee9Sbenno }
51160a32ee9Sbenno
51260a32ee9Sbenno /*
51360a32ee9Sbenno * Reallocate a file list in chunks of FLIST_CHUNK_SIZE;
51460a32ee9Sbenno * Returns zero on failure, non-zero on success.
51560a32ee9Sbenno */
51660a32ee9Sbenno static int
flist_realloc(struct flist ** fl,size_t * sz,size_t * max)517ba617adaSbenno flist_realloc(struct flist **fl, size_t *sz, size_t *max)
51860a32ee9Sbenno {
51960a32ee9Sbenno void *pp;
52060a32ee9Sbenno
52160a32ee9Sbenno if (*sz + 1 <= *max) {
52260a32ee9Sbenno (*sz)++;
52360a32ee9Sbenno return 1;
52460a32ee9Sbenno }
52560a32ee9Sbenno
52660a32ee9Sbenno pp = recallocarray(*fl, *max,
52760a32ee9Sbenno *max + FLIST_CHUNK_SIZE, sizeof(struct flist));
528f1dcb30aSderaadt if (pp == NULL) {
529b2a7eac7Sbenno ERR("recallocarray");
53060a32ee9Sbenno return 0;
53160a32ee9Sbenno }
53260a32ee9Sbenno *fl = pp;
53360a32ee9Sbenno *max += FLIST_CHUNK_SIZE;
53460a32ee9Sbenno (*sz)++;
53560a32ee9Sbenno return 1;
53660a32ee9Sbenno }
53760a32ee9Sbenno
53860a32ee9Sbenno /*
53960a32ee9Sbenno * Copy a regular or symbolic link file "path" into "f".
54060a32ee9Sbenno * This handles the correct path creation and symbolic linking.
54160a32ee9Sbenno * Returns zero on failure, non-zero on success.
54260a32ee9Sbenno */
54360a32ee9Sbenno static int
flist_append(struct flist * f,struct stat * st,const char * path)544ba617adaSbenno flist_append(struct flist *f, struct stat *st, const char *path)
54560a32ee9Sbenno {
54660a32ee9Sbenno
54760a32ee9Sbenno /*
54860a32ee9Sbenno * Copy the full path for local addressing and transmit
54960a32ee9Sbenno * only the filename part for the receiver.
55060a32ee9Sbenno */
55160a32ee9Sbenno
552f1dcb30aSderaadt if ((f->path = strdup(path)) == NULL) {
553b2a7eac7Sbenno ERR("strdup");
55460a32ee9Sbenno return 0;
55560a32ee9Sbenno }
55660a32ee9Sbenno
557f1dcb30aSderaadt if ((f->wpath = strrchr(f->path, '/')) == NULL)
55860a32ee9Sbenno f->wpath = f->path;
55960a32ee9Sbenno else
56060a32ee9Sbenno f->wpath++;
56160a32ee9Sbenno
56260a32ee9Sbenno /*
56360a32ee9Sbenno * On the receiving end, we'll strip out all bits on the
56460a32ee9Sbenno * mode except for the file permissions.
56560a32ee9Sbenno * No need to warn about it here.
56660a32ee9Sbenno */
56760a32ee9Sbenno
56860a32ee9Sbenno flist_copy_stat(f, st);
56960a32ee9Sbenno
57060a32ee9Sbenno /* Optionally copy link information. */
57160a32ee9Sbenno
57260a32ee9Sbenno if (S_ISLNK(st->st_mode)) {
573ba617adaSbenno f->link = symlink_read(f->path);
574f1dcb30aSderaadt if (f->link == NULL) {
575b2a7eac7Sbenno ERRX1("symlink_read");
57660a32ee9Sbenno return 0;
57760a32ee9Sbenno }
57860a32ee9Sbenno }
57960a32ee9Sbenno
58060a32ee9Sbenno return 1;
58160a32ee9Sbenno }
58260a32ee9Sbenno
58360a32ee9Sbenno /*
58460a32ee9Sbenno * Receive a file list from the wire, filling in length "sz" (which may
58560a32ee9Sbenno * possibly be zero) and list "flp" on success.
58660a32ee9Sbenno * Return zero on failure, non-zero on success.
58760a32ee9Sbenno */
58860a32ee9Sbenno int
flist_recv(struct sess * sess,int fd,struct flist ** flp,size_t * sz)58960a32ee9Sbenno flist_recv(struct sess *sess, int fd, struct flist **flp, size_t *sz)
59060a32ee9Sbenno {
59160a32ee9Sbenno struct flist *fl = NULL;
59260a32ee9Sbenno struct flist *ff;
59360a32ee9Sbenno const struct flist *fflast = NULL;
5948f34fbc5Sflorian size_t flsz = 0, flmax = 0, lsz, gidsz = 0, uidsz = 0;
59560a32ee9Sbenno uint8_t flag;
596173dc874Sderaadt char last[PATH_MAX];
597aa1dcd86Sderaadt int64_t lval; /* temporary values... */
59860a32ee9Sbenno int32_t ival;
59960ac34c7Sderaadt uint32_t uival;
6008f34fbc5Sflorian struct ident *gids = NULL, *uids = NULL;
60160a32ee9Sbenno
60260a32ee9Sbenno last[0] = '\0';
60360a32ee9Sbenno
60460a32ee9Sbenno for (;;) {
60560a32ee9Sbenno if (!io_read_byte(sess, fd, &flag)) {
606b2a7eac7Sbenno ERRX1("io_read_byte");
60760a32ee9Sbenno goto out;
608f1dcb30aSderaadt } else if (flag == 0)
60960a32ee9Sbenno break;
61060a32ee9Sbenno
611ba617adaSbenno if (!flist_realloc(&fl, &flsz, &flmax)) {
612b2a7eac7Sbenno ERRX1("flist_realloc");
61360a32ee9Sbenno goto out;
61460a32ee9Sbenno }
61560a32ee9Sbenno
61660a32ee9Sbenno ff = &fl[flsz - 1];
61760a32ee9Sbenno fflast = flsz > 1 ? &fl[flsz - 2] : NULL;
61860a32ee9Sbenno
61960a32ee9Sbenno /* Filename first. */
62060a32ee9Sbenno
62160a32ee9Sbenno if (!flist_recv_name(sess, fd, ff, flag, last)) {
622b2a7eac7Sbenno ERRX1("flist_recv_name");
62360a32ee9Sbenno goto out;
62460a32ee9Sbenno }
62560a32ee9Sbenno
62660a32ee9Sbenno /* Read the file size. */
62760a32ee9Sbenno
628aa1dcd86Sderaadt if (!io_read_long(sess, fd, &lval)) {
629b2a7eac7Sbenno ERRX1("io_read_long");
63060a32ee9Sbenno goto out;
63160a32ee9Sbenno }
63260a32ee9Sbenno ff->st.size = lval;
63360a32ee9Sbenno
63460a32ee9Sbenno /* Read the modification time. */
63560a32ee9Sbenno
636*5fe4a96fSclaudio if (!(flag & FLIST_TIME_SAME)) {
637aa1dcd86Sderaadt if (!io_read_uint(sess, fd, &uival)) {
638e7e8e562Sclaudio ERRX1("io_read_uint");
63960a32ee9Sbenno goto out;
64060a32ee9Sbenno }
64160ac34c7Sderaadt ff->st.mtime = uival; /* beyond 2038 */
642f1dcb30aSderaadt } else if (fflast == NULL) {
643*5fe4a96fSclaudio ff->st.mtime = 0;
64460a32ee9Sbenno } else
64560a32ee9Sbenno ff->st.mtime = fflast->st.mtime;
64660a32ee9Sbenno
64760a32ee9Sbenno /* Read the file mode. */
64860a32ee9Sbenno
649*5fe4a96fSclaudio if (!(flag & FLIST_MODE_SAME)) {
650aa1dcd86Sderaadt if (!io_read_uint(sess, fd, &uival)) {
651e7e8e562Sclaudio ERRX1("io_read_uint");
65260a32ee9Sbenno goto out;
65360a32ee9Sbenno }
654aa1dcd86Sderaadt ff->st.mode = uival;
655f1dcb30aSderaadt } else if (fflast == NULL) {
656*5fe4a96fSclaudio ff->st.mode = 0;
65760a32ee9Sbenno } else
65860a32ee9Sbenno ff->st.mode = fflast->st.mode;
65960a32ee9Sbenno
6608f34fbc5Sflorian /* Conditional part: uid. */
6618f34fbc5Sflorian
6628f34fbc5Sflorian if (sess->opts->preserve_uids) {
663*5fe4a96fSclaudio if (!(flag & FLIST_UID_SAME)) {
664aa1dcd86Sderaadt if (!io_read_uint(sess, fd, &uival)) {
665b2a7eac7Sbenno ERRX1("io_read_int");
6668f34fbc5Sflorian goto out;
6678f34fbc5Sflorian }
668aa1dcd86Sderaadt ff->st.uid = uival;
6698f34fbc5Sflorian } else if (fflast == NULL) {
670*5fe4a96fSclaudio ff->st.uid = 0;
6718f34fbc5Sflorian } else
6728f34fbc5Sflorian ff->st.uid = fflast->st.uid;
6738f34fbc5Sflorian }
6748f34fbc5Sflorian
675ea2cd662Sbenno /* Conditional part: gid. */
676ea2cd662Sbenno
677ea2cd662Sbenno if (sess->opts->preserve_gids) {
678*5fe4a96fSclaudio if (!(flag & FLIST_GID_SAME)) {
679aa1dcd86Sderaadt if (!io_read_uint(sess, fd, &uival)) {
680e7e8e562Sclaudio ERRX1("io_read_uint");
681ea2cd662Sbenno goto out;
682ea2cd662Sbenno }
683aa1dcd86Sderaadt ff->st.gid = uival;
684aacc588eSbenno } else if (fflast == NULL) {
685*5fe4a96fSclaudio ff->st.gid = 0;
686ea2cd662Sbenno } else
687ea2cd662Sbenno ff->st.gid = fflast->st.gid;
688ea2cd662Sbenno }
689ea2cd662Sbenno
69080ee851fSflorian /* Conditional part: devices & special files. */
691434f41cdSflorian
692434f41cdSflorian if ((sess->opts->devices && (S_ISBLK(ff->st.mode) ||
693434f41cdSflorian S_ISCHR(ff->st.mode))) ||
694434f41cdSflorian (sess->opts->specials && (S_ISFIFO(ff->st.mode) ||
695434f41cdSflorian S_ISSOCK(ff->st.mode)))) {
696*5fe4a96fSclaudio if (!(flag & FLIST_RDEV_SAME)) {
697434f41cdSflorian if (!io_read_int(sess, fd, &ival)) {
698b2a7eac7Sbenno ERRX1("io_read_int");
699434f41cdSflorian goto out;
700434f41cdSflorian }
701434f41cdSflorian ff->st.rdev = ival;
702434f41cdSflorian } else if (fflast == NULL) {
703*5fe4a96fSclaudio ff->st.rdev = 0;
704434f41cdSflorian } else
705434f41cdSflorian ff->st.rdev = fflast->st.rdev;
706434f41cdSflorian }
707434f41cdSflorian
708ea2cd662Sbenno /* Conditional part: link. */
70960a32ee9Sbenno
71060a32ee9Sbenno if (S_ISLNK(ff->st.mode) &&
71160a32ee9Sbenno sess->opts->preserve_links) {
71260a32ee9Sbenno if (!io_read_size(sess, fd, &lsz)) {
713b2a7eac7Sbenno ERRX1("io_read_size");
71460a32ee9Sbenno goto out;
715f1dcb30aSderaadt } else if (lsz == 0) {
716b2a7eac7Sbenno ERRX("empty link name");
71760a32ee9Sbenno goto out;
71860a32ee9Sbenno }
71960a32ee9Sbenno ff->link = calloc(lsz + 1, 1);
720f1dcb30aSderaadt if (ff->link == NULL) {
721b2a7eac7Sbenno ERR("calloc");
72260a32ee9Sbenno goto out;
72360a32ee9Sbenno }
72460a32ee9Sbenno if (!io_read_buf(sess, fd, ff->link, lsz)) {
725b2a7eac7Sbenno ERRX1("io_read_buf");
72660a32ee9Sbenno goto out;
72760a32ee9Sbenno }
72860a32ee9Sbenno }
72960a32ee9Sbenno
730b2a7eac7Sbenno LOG3("%s: received file metadata: "
731434f41cdSflorian "size %jd, mtime %jd, mode %o, rdev (%d, %d)",
73260a32ee9Sbenno ff->path, (intmax_t)ff->st.size,
733434f41cdSflorian (intmax_t)ff->st.mtime, ff->st.mode,
734434f41cdSflorian major(ff->st.rdev), minor(ff->st.rdev));
73560a32ee9Sbenno
73660a32ee9Sbenno if (S_ISREG(ff->st.mode))
73760a32ee9Sbenno sess->total_size += ff->st.size;
73860a32ee9Sbenno }
73960a32ee9Sbenno
7408f34fbc5Sflorian /* Conditionally read the user/group list. */
7418f34fbc5Sflorian
74244dad8d1Sbenno if (sess->opts->preserve_uids && !sess->opts->numeric_ids) {
7438f34fbc5Sflorian if (!idents_recv(sess, fd, &uids, &uidsz)) {
744b2a7eac7Sbenno ERRX1("idents_recv");
7458f34fbc5Sflorian goto out;
7468f34fbc5Sflorian }
747b2a7eac7Sbenno LOG2("received uid list: %zu", uidsz);
7488f34fbc5Sflorian }
7496a02c679Sbenno
75044dad8d1Sbenno if (sess->opts->preserve_gids && !sess->opts->numeric_ids) {
751997d99f9Sbenno if (!idents_recv(sess, fd, &gids, &gidsz)) {
752b2a7eac7Sbenno ERRX1("idents_recv");
7536a02c679Sbenno goto out;
7546a02c679Sbenno }
755b2a7eac7Sbenno LOG2("received gid list: %zu", gidsz);
7566a02c679Sbenno }
7576a02c679Sbenno
75860a32ee9Sbenno /* Remember to order the received list. */
75960a32ee9Sbenno
760b2a7eac7Sbenno LOG2("received file metadata list: %zu", flsz);
76160a32ee9Sbenno qsort(fl, flsz, sizeof(struct flist), flist_cmp);
76260a32ee9Sbenno flist_topdirs(sess, fl, flsz);
76360a32ee9Sbenno *sz = flsz;
76460a32ee9Sbenno *flp = fl;
7656a02c679Sbenno
7668f34fbc5Sflorian /* Conditionally remap and reassign identifiers. */
7678f34fbc5Sflorian
76844dad8d1Sbenno if (sess->opts->preserve_uids && !sess->opts->numeric_ids) {
7698f34fbc5Sflorian idents_remap(sess, 0, uids, uidsz);
7708f34fbc5Sflorian idents_assign_uid(sess, fl, flsz, uids, uidsz);
7718f34fbc5Sflorian }
7726a02c679Sbenno
77344dad8d1Sbenno if (sess->opts->preserve_gids && !sess->opts->numeric_ids) {
7748f34fbc5Sflorian idents_remap(sess, 1, gids, gidsz);
7758f34fbc5Sflorian idents_assign_gid(sess, fl, flsz, gids, gidsz);
7766a02c679Sbenno }
7776a02c679Sbenno
778997d99f9Sbenno idents_free(gids, gidsz);
7798f34fbc5Sflorian idents_free(uids, uidsz);
78060a32ee9Sbenno return 1;
78160a32ee9Sbenno out:
78260a32ee9Sbenno flist_free(fl, flsz);
783997d99f9Sbenno idents_free(gids, gidsz);
7848f34fbc5Sflorian idents_free(uids, uidsz);
78560a32ee9Sbenno *sz = 0;
78660a32ee9Sbenno *flp = NULL;
78760a32ee9Sbenno return 0;
78860a32ee9Sbenno }
78960a32ee9Sbenno
79060a32ee9Sbenno /*
79160a32ee9Sbenno * Generate a flist possibly-recursively given a file root, which may
79260a32ee9Sbenno * also be a regular file or symlink.
79360a32ee9Sbenno * On success, augments the generated list in "flp" of length "sz".
79460a32ee9Sbenno * Returns zero on failure, non-zero on success.
79560a32ee9Sbenno */
79660a32ee9Sbenno static int
flist_gen_dirent(struct sess * sess,char * root,struct flist ** fl,size_t * sz,size_t * max)797df3fb795Sbenno flist_gen_dirent(struct sess *sess, char *root, struct flist **fl, size_t *sz,
798df3fb795Sbenno size_t *max)
79960a32ee9Sbenno {
80060a32ee9Sbenno char *cargv[2], *cp;
801447e3174Sbenno int rc = 0, flag;
80260a32ee9Sbenno FTS *fts;
80360a32ee9Sbenno FTSENT *ent;
80460a32ee9Sbenno struct flist *f;
805447e3174Sbenno size_t i, flsz = 0, nxdev = 0, stripdir;
806447e3174Sbenno dev_t *newxdev, *xdev = NULL;
80760a32ee9Sbenno struct stat st;
80860a32ee9Sbenno
80960a32ee9Sbenno cargv[0] = root;
81060a32ee9Sbenno cargv[1] = NULL;
81160a32ee9Sbenno
81260a32ee9Sbenno /*
81360a32ee9Sbenno * If we're a file, then revert to the same actions we use for
81460a32ee9Sbenno * the non-recursive scan.
81560a32ee9Sbenno */
81660a32ee9Sbenno
817f1dcb30aSderaadt if (lstat(root, &st) == -1) {
818b2a7eac7Sbenno ERR("%s: lstat", root);
81960a32ee9Sbenno return 0;
82060a32ee9Sbenno } else if (S_ISREG(st.st_mode)) {
82157987d16Sclaudio /* filter files */
82257987d16Sclaudio if (rules_match(root, 0) == -1) {
82357987d16Sclaudio WARNX("%s: skipping excluded file", root);
82457987d16Sclaudio return 1;
82557987d16Sclaudio }
826ba617adaSbenno if (!flist_realloc(fl, sz, max)) {
827b2a7eac7Sbenno ERRX1("flist_realloc");
82860a32ee9Sbenno return 0;
82960a32ee9Sbenno }
83060a32ee9Sbenno f = &(*fl)[(*sz) - 1];
831f1dcb30aSderaadt assert(f != NULL);
83260a32ee9Sbenno
833ba617adaSbenno if (!flist_append(f, &st, root)) {
834b2a7eac7Sbenno ERRX1("flist_append");
83560a32ee9Sbenno return 0;
836ac523f9aSbenno }
83760a32ee9Sbenno return 1;
83860a32ee9Sbenno } else if (S_ISLNK(st.st_mode)) {
83960a32ee9Sbenno if (!sess->opts->preserve_links) {
840b2a7eac7Sbenno WARNX("%s: skipping symlink", root);
84160a32ee9Sbenno return 1;
84257987d16Sclaudio }
84357987d16Sclaudio /* filter files */
84457987d16Sclaudio if (rules_match(root, 0) == -1) {
84557987d16Sclaudio WARNX("%s: skipping excluded symlink", root);
84657987d16Sclaudio return 1;
84757987d16Sclaudio }
84857987d16Sclaudio if (!flist_realloc(fl, sz, max)) {
849b2a7eac7Sbenno ERRX1("flist_realloc");
85060a32ee9Sbenno return 0;
85160a32ee9Sbenno }
85260a32ee9Sbenno f = &(*fl)[(*sz) - 1];
853f1dcb30aSderaadt assert(f != NULL);
85460a32ee9Sbenno
855ba617adaSbenno if (!flist_append(f, &st, root)) {
856b2a7eac7Sbenno ERRX1("flist_append");
85760a32ee9Sbenno return 0;
858ac523f9aSbenno }
85960a32ee9Sbenno return 1;
86060a32ee9Sbenno } else if (!S_ISDIR(st.st_mode)) {
861b2a7eac7Sbenno WARNX("%s: skipping special", root);
86260a32ee9Sbenno return 1;
86360a32ee9Sbenno }
86460a32ee9Sbenno
86560a32ee9Sbenno /*
86660a32ee9Sbenno * If we end with a slash, it means that we're not supposed to
86760a32ee9Sbenno * copy the directory part itself---only the contents.
86860a32ee9Sbenno * So set "stripdir" to be what we take out.
86960a32ee9Sbenno */
87060a32ee9Sbenno
87160a32ee9Sbenno stripdir = strlen(root);
87260a32ee9Sbenno assert(stripdir > 0);
873f1dcb30aSderaadt if (root[stripdir - 1] != '/')
87460a32ee9Sbenno stripdir = 0;
87560a32ee9Sbenno
87660a32ee9Sbenno /*
87760a32ee9Sbenno * If we're not stripping anything, then see if we need to strip
87860a32ee9Sbenno * out the leading material in the path up to and including the
87960a32ee9Sbenno * last directory component.
88060a32ee9Sbenno */
88160a32ee9Sbenno
882f1dcb30aSderaadt if (stripdir == 0)
883f1dcb30aSderaadt if ((cp = strrchr(root, '/')) != NULL)
88460a32ee9Sbenno stripdir = cp - root + 1;
88560a32ee9Sbenno
88660a32ee9Sbenno /*
88760a32ee9Sbenno * If we're recursive, then we need to take down all of the
88860a32ee9Sbenno * files and directory components, so use fts(3).
88960a32ee9Sbenno * Copying the information file-by-file into the flstat.
89060a32ee9Sbenno * We'll make sense of it in flist_send.
89160a32ee9Sbenno */
89260a32ee9Sbenno
893f1dcb30aSderaadt if ((fts = fts_open(cargv, FTS_PHYSICAL, NULL)) == NULL) {
894b2a7eac7Sbenno ERR("fts_open");
89560a32ee9Sbenno return 0;
89660a32ee9Sbenno }
89760a32ee9Sbenno
89860a32ee9Sbenno errno = 0;
899f1dcb30aSderaadt while ((ent = fts_read(fts)) != NULL) {
90060a32ee9Sbenno if (!flist_fts_check(sess, ent)) {
90160a32ee9Sbenno errno = 0;
90260a32ee9Sbenno continue;
90360a32ee9Sbenno }
90460a32ee9Sbenno
90560a32ee9Sbenno /* We don't allow symlinks without -l. */
90660a32ee9Sbenno
907f1dcb30aSderaadt assert(ent->fts_statp != NULL);
90860a32ee9Sbenno if (S_ISLNK(ent->fts_statp->st_mode) &&
90960a32ee9Sbenno !sess->opts->preserve_links) {
910b2a7eac7Sbenno WARNX("%s: skipping symlink", ent->fts_path);
91160a32ee9Sbenno continue;
91260a32ee9Sbenno }
91360a32ee9Sbenno
9141c3d4160Sbket /*
9151c3d4160Sbket * If rsync is told to avoid crossing a filesystem
9161c3d4160Sbket * boundary when recursing, then replace all mount point
9171c3d4160Sbket * directories with empty directories. The latter is
9181c3d4160Sbket * prevented by telling rsync multiple times to avoid
9191c3d4160Sbket * crossing a filesystem boundary when recursing.
9201c3d4160Sbket * Replacing mount point directories is tricky. We need
9211c3d4160Sbket * to sort out which directories to include. As such,
9221c3d4160Sbket * keep track of unique device inodes, and use these for
9231c3d4160Sbket * comparison.
9241c3d4160Sbket */
9251c3d4160Sbket
9261c3d4160Sbket if (sess->opts->one_file_system &&
9271c3d4160Sbket ent->fts_statp->st_dev != st.st_dev) {
9281c3d4160Sbket if (sess->opts->one_file_system > 1 ||
9291c3d4160Sbket !S_ISDIR(ent->fts_statp->st_mode))
9301c3d4160Sbket continue;
9311c3d4160Sbket
9321c3d4160Sbket flag = 0;
9331c3d4160Sbket for (i = 0; i < nxdev; i++)
9341c3d4160Sbket if (xdev[i] == ent->fts_statp->st_dev) {
9351c3d4160Sbket flag = 1;
9361c3d4160Sbket break;
9371c3d4160Sbket }
9381c3d4160Sbket if (flag)
9391c3d4160Sbket continue;
9401c3d4160Sbket
941447e3174Sbenno if ((newxdev = reallocarray(xdev, nxdev + 1,
942447e3174Sbenno sizeof(dev_t))) == NULL) {
943447e3174Sbenno ERRX1("reallocarray");
9441c3d4160Sbket goto out;
9451c3d4160Sbket }
946447e3174Sbenno xdev = newxdev;
9471c3d4160Sbket xdev[nxdev] = ent->fts_statp->st_dev;
9481c3d4160Sbket nxdev++;
9491c3d4160Sbket }
9501c3d4160Sbket
95157987d16Sclaudio /* filter files */
95257987d16Sclaudio if (rules_match(ent->fts_path + stripdir,
95357987d16Sclaudio (ent->fts_info == FTS_D)) == -1) {
95457987d16Sclaudio WARNX("%s: skipping excluded file",
95557987d16Sclaudio ent->fts_path + stripdir);
95657987d16Sclaudio fts_set(fts, ent, FTS_SKIP);
95757987d16Sclaudio continue;
95857987d16Sclaudio }
95957987d16Sclaudio
96060a32ee9Sbenno /* Allocate a new file entry. */
96160a32ee9Sbenno
962ba617adaSbenno if (!flist_realloc(fl, sz, max)) {
963b2a7eac7Sbenno ERRX1("flist_realloc");
96460a32ee9Sbenno goto out;
96560a32ee9Sbenno }
96660a32ee9Sbenno flsz++;
96760a32ee9Sbenno f = &(*fl)[*sz - 1];
96860a32ee9Sbenno
96960a32ee9Sbenno /* Our path defaults to "." for the root. */
97060a32ee9Sbenno
97108a7862dSderaadt if (ent->fts_path[stripdir] == '\0') {
97295af8abfSderaadt if (asprintf(&f->path, "%s.", ent->fts_path) == -1) {
973b2a7eac7Sbenno ERR("asprintf");
97460a32ee9Sbenno f->path = NULL;
97560a32ee9Sbenno goto out;
97660a32ee9Sbenno }
97760a32ee9Sbenno } else {
978f1dcb30aSderaadt if ((f->path = strdup(ent->fts_path)) == NULL) {
979b2a7eac7Sbenno ERR("strdup");
98060a32ee9Sbenno goto out;
98160a32ee9Sbenno }
98260a32ee9Sbenno }
98360a32ee9Sbenno
98460a32ee9Sbenno f->wpath = f->path + stripdir;
98560a32ee9Sbenno flist_copy_stat(f, ent->fts_statp);
98660a32ee9Sbenno
98760a32ee9Sbenno /* Optionally copy link information. */
98860a32ee9Sbenno
98960a32ee9Sbenno if (S_ISLNK(ent->fts_statp->st_mode)) {
990e812f350Sclaudio f->link = symlink_read(ent->fts_accpath);
991f1dcb30aSderaadt if (f->link == NULL) {
992b2a7eac7Sbenno ERRX1("symlink_read");
99360a32ee9Sbenno goto out;
99460a32ee9Sbenno }
99560a32ee9Sbenno }
99660a32ee9Sbenno
99760a32ee9Sbenno /* Reset errno for next fts_read() call. */
99860a32ee9Sbenno errno = 0;
99960a32ee9Sbenno }
100060a32ee9Sbenno if (errno) {
1001b2a7eac7Sbenno ERR("fts_read");
100260a32ee9Sbenno goto out;
1003ac523f9aSbenno }
100460a32ee9Sbenno
1005b2a7eac7Sbenno LOG3("generated %zu filenames: %s", flsz, root);
100660a32ee9Sbenno rc = 1;
100760a32ee9Sbenno out:
100860a32ee9Sbenno fts_close(fts);
10091c3d4160Sbket free(xdev);
101060a32ee9Sbenno return rc;
101160a32ee9Sbenno }
101260a32ee9Sbenno
101360a32ee9Sbenno /*
101460a32ee9Sbenno * Generate a flist recursively given the array of directories (or
101560a32ee9Sbenno * files, symlinks, doesn't matter) specified in argv (argc >0).
101660a32ee9Sbenno * On success, stores the generated list in "flp" with length "sz",
101760a32ee9Sbenno * which may be zero.
101860a32ee9Sbenno * Returns zero on failure, non-zero on success.
101960a32ee9Sbenno */
102060a32ee9Sbenno static int
flist_gen_dirs(struct sess * sess,size_t argc,char ** argv,struct flist ** flp,size_t * sz)1021df3fb795Sbenno flist_gen_dirs(struct sess *sess, size_t argc, char **argv, struct flist **flp,
1022df3fb795Sbenno size_t *sz)
102360a32ee9Sbenno {
102460a32ee9Sbenno size_t i, max = 0;
102560a32ee9Sbenno
102660a32ee9Sbenno for (i = 0; i < argc; i++)
102760a32ee9Sbenno if (!flist_gen_dirent(sess, argv[i], flp, sz, &max))
102860a32ee9Sbenno break;
102960a32ee9Sbenno
103060a32ee9Sbenno if (i == argc) {
1031b2a7eac7Sbenno LOG2("recursively generated %zu filenames", *sz);
103260a32ee9Sbenno return 1;
103360a32ee9Sbenno }
103460a32ee9Sbenno
1035b2a7eac7Sbenno ERRX1("flist_gen_dirent");
103660a32ee9Sbenno flist_free(*flp, max);
103760a32ee9Sbenno *flp = NULL;
103860a32ee9Sbenno *sz = 0;
103960a32ee9Sbenno return 0;
104060a32ee9Sbenno }
104160a32ee9Sbenno
104260a32ee9Sbenno /*
104360a32ee9Sbenno * Generate list of files from the command-line argc (>0) and argv.
104460a32ee9Sbenno * On success, stores the generated list in "flp" with length "sz",
104560a32ee9Sbenno * which may be zero.
104660a32ee9Sbenno * Returns zero on failure, non-zero on success.
104760a32ee9Sbenno */
104860a32ee9Sbenno static int
flist_gen_files(struct sess * sess,size_t argc,char ** argv,struct flist ** flp,size_t * sz)1049df3fb795Sbenno flist_gen_files(struct sess *sess, size_t argc, char **argv,
1050df3fb795Sbenno struct flist **flp, size_t *sz)
105160a32ee9Sbenno {
105260a32ee9Sbenno struct flist *fl = NULL, *f;
105360a32ee9Sbenno size_t i, flsz = 0;
105460a32ee9Sbenno struct stat st;
105560a32ee9Sbenno
105660a32ee9Sbenno assert(argc);
105760a32ee9Sbenno
1058f1dcb30aSderaadt if ((fl = calloc(argc, sizeof(struct flist))) == NULL) {
1059b2a7eac7Sbenno ERR("calloc");
106060a32ee9Sbenno return 0;
106160a32ee9Sbenno }
106260a32ee9Sbenno
106360a32ee9Sbenno for (i = 0; i < argc; i++) {
106408a7862dSderaadt if (argv[i][0] == '\0')
106560a32ee9Sbenno continue;
1066f1dcb30aSderaadt if (lstat(argv[i], &st) == -1) {
1067b2a7eac7Sbenno ERR("%s: lstat", argv[i]);
106860a32ee9Sbenno goto out;
106960a32ee9Sbenno }
107060a32ee9Sbenno
107160a32ee9Sbenno /*
107260a32ee9Sbenno * File type checks.
107360a32ee9Sbenno * In non-recursive mode, we don't accept directories.
107460a32ee9Sbenno * We also skip symbolic links without -l.
107560a32ee9Sbenno * Beyond that, we only accept regular files.
107660a32ee9Sbenno */
107760a32ee9Sbenno
107860a32ee9Sbenno if (S_ISDIR(st.st_mode)) {
1079b2a7eac7Sbenno WARNX("%s: skipping directory", argv[i]);
108060a32ee9Sbenno continue;
108160a32ee9Sbenno } else if (S_ISLNK(st.st_mode)) {
108260a32ee9Sbenno if (!sess->opts->preserve_links) {
1083b2a7eac7Sbenno WARNX("%s: skipping symlink", argv[i]);
108460a32ee9Sbenno continue;
108560a32ee9Sbenno }
108660a32ee9Sbenno } else if (!S_ISREG(st.st_mode)) {
1087b2a7eac7Sbenno WARNX("%s: skipping special", argv[i]);
108860a32ee9Sbenno continue;
108960a32ee9Sbenno }
109060a32ee9Sbenno
109157987d16Sclaudio /* filter files */
109257987d16Sclaudio if (rules_match(argv[i], S_ISDIR(st.st_mode)) == -1) {
109357987d16Sclaudio WARNX("%s: skipping excluded file", argv[i]);
109457987d16Sclaudio continue;
109557987d16Sclaudio }
1096df3fb795Sbenno
109760a32ee9Sbenno f = &fl[flsz++];
1098f1dcb30aSderaadt assert(f != NULL);
109960a32ee9Sbenno
110060a32ee9Sbenno /* Add this file to our file-system worldview. */
110160a32ee9Sbenno
1102ba617adaSbenno if (!flist_append(f, &st, argv[i])) {
1103b2a7eac7Sbenno ERRX1("flist_append");
110460a32ee9Sbenno goto out;
110560a32ee9Sbenno }
110660a32ee9Sbenno }
110760a32ee9Sbenno
1108b2a7eac7Sbenno LOG2("non-recursively generated %zu filenames", flsz);
110960a32ee9Sbenno *sz = flsz;
111060a32ee9Sbenno *flp = fl;
111160a32ee9Sbenno return 1;
111260a32ee9Sbenno out:
111360a32ee9Sbenno flist_free(fl, argc);
111460a32ee9Sbenno *sz = 0;
111560a32ee9Sbenno *flp = NULL;
111660a32ee9Sbenno return 0;
111760a32ee9Sbenno }
111860a32ee9Sbenno
111960a32ee9Sbenno /*
112060a32ee9Sbenno * Generate a sorted, de-duplicated list of file metadata.
112160a32ee9Sbenno * In non-recursive mode (the default), we use only the files we're
112260a32ee9Sbenno * given.
112360a32ee9Sbenno * Otherwise, directories are recursively examined.
112460a32ee9Sbenno * Returns zero on failure, non-zero on success.
112560a32ee9Sbenno * On success, "fl" will need to be freed with flist_free().
112660a32ee9Sbenno */
112760a32ee9Sbenno int
flist_gen(struct sess * sess,size_t argc,char ** argv,struct flist ** flp,size_t * sz)1128df3fb795Sbenno flist_gen(struct sess *sess, size_t argc, char **argv, struct flist **flp,
1129df3fb795Sbenno size_t *sz)
113060a32ee9Sbenno {
113160a32ee9Sbenno int rc;
113260a32ee9Sbenno
113360a32ee9Sbenno assert(argc > 0);
113460a32ee9Sbenno rc = sess->opts->recursive ?
113560a32ee9Sbenno flist_gen_dirs(sess, argc, argv, flp, sz) :
113660a32ee9Sbenno flist_gen_files(sess, argc, argv, flp, sz);
113760a32ee9Sbenno
113860a32ee9Sbenno /* After scanning, lock our file-system view. */
113960a32ee9Sbenno
1140ac523f9aSbenno if (!rc)
114160a32ee9Sbenno return 0;
114260a32ee9Sbenno
114360a32ee9Sbenno qsort(*flp, *sz, sizeof(struct flist), flist_cmp);
114460a32ee9Sbenno
1145ba617adaSbenno if (flist_dedupe(flp, sz)) {
114660a32ee9Sbenno flist_topdirs(sess, *flp, *sz);
114760a32ee9Sbenno return 1;
114860a32ee9Sbenno }
114960a32ee9Sbenno
1150b2a7eac7Sbenno ERRX1("flist_dedupe");
115160a32ee9Sbenno flist_free(*flp, *sz);
115260a32ee9Sbenno *flp = NULL;
115360a32ee9Sbenno *sz = 0;
115460a32ee9Sbenno return 0;
115560a32ee9Sbenno }
115660a32ee9Sbenno
115760a32ee9Sbenno /*
115860a32ee9Sbenno * Generate a list of files in root to delete that are within the
115960a32ee9Sbenno * top-level directories stipulated by "wfl".
116060a32ee9Sbenno * Only handles symbolic links, directories, and regular files.
116160a32ee9Sbenno * Returns zero on failure (fl and flsz will be NULL and zero), non-zero
116260a32ee9Sbenno * on success.
116360a32ee9Sbenno * On success, "fl" will need to be freed with flist_free().
116460a32ee9Sbenno */
116560a32ee9Sbenno int
flist_gen_dels(struct sess * sess,const char * root,struct flist ** fl,size_t * sz,const struct flist * wfl,size_t wflsz)1166df3fb795Sbenno flist_gen_dels(struct sess *sess, const char *root, struct flist **fl,
1167df3fb795Sbenno size_t *sz, const struct flist *wfl, size_t wflsz)
116860a32ee9Sbenno {
116960a32ee9Sbenno char **cargv = NULL;
11701c3d4160Sbket int rc = 0, c, flag;
117160a32ee9Sbenno FTS *fts = NULL;
117260a32ee9Sbenno FTSENT *ent;
117360a32ee9Sbenno struct flist *f;
11741c3d4160Sbket struct stat st;
117560a32ee9Sbenno size_t cargvs = 0, i, j, max = 0, stripdir;
117660a32ee9Sbenno ENTRY hent;
117760a32ee9Sbenno ENTRY *hentp;
117860a32ee9Sbenno
117960a32ee9Sbenno *fl = NULL;
118060a32ee9Sbenno *sz = 0;
118160a32ee9Sbenno
118260a32ee9Sbenno /* Only run this code when we're recursive. */
118360a32ee9Sbenno
118460a32ee9Sbenno if (!sess->opts->recursive)
118560a32ee9Sbenno return 1;
118660a32ee9Sbenno
118760a32ee9Sbenno /*
118860a32ee9Sbenno * Gather up all top-level directories for scanning.
118960a32ee9Sbenno * This is stipulated by rsync's --delete behaviour, where we
119060a32ee9Sbenno * only delete things in the top-level directories given on the
119160a32ee9Sbenno * command line.
119260a32ee9Sbenno */
119360a32ee9Sbenno
119460a32ee9Sbenno assert(wflsz > 0);
119560a32ee9Sbenno for (i = 0; i < wflsz; i++)
119660a32ee9Sbenno if (FLSTAT_TOP_DIR & wfl[i].st.flags)
119760a32ee9Sbenno cargvs++;
1198f1dcb30aSderaadt if (cargvs == 0)
119960a32ee9Sbenno return 1;
120060a32ee9Sbenno
1201f1dcb30aSderaadt if ((cargv = calloc(cargvs + 1, sizeof(char *))) == NULL) {
1202b2a7eac7Sbenno ERR("calloc");
120360a32ee9Sbenno return 0;
120460a32ee9Sbenno }
120560a32ee9Sbenno
120660a32ee9Sbenno /*
120760a32ee9Sbenno * If we're given just a "." as the first entry, that means
120860a32ee9Sbenno * we're doing a relative copy with a trailing slash.
120960a32ee9Sbenno * Special-case this just for the sake of simplicity.
121060a32ee9Sbenno * Otherwise, look through all top-levels.
121160a32ee9Sbenno */
121260a32ee9Sbenno
1213f1dcb30aSderaadt if (wflsz && strcmp(wfl[0].wpath, ".") == 0) {
1214f1dcb30aSderaadt assert(cargvs == 1);
121560a32ee9Sbenno assert(S_ISDIR(wfl[0].st.mode));
121695af8abfSderaadt if (asprintf(&cargv[0], "%s/", root) == -1) {
1217b2a7eac7Sbenno ERR("asprintf");
121860a32ee9Sbenno cargv[0] = NULL;
121960a32ee9Sbenno goto out;
122060a32ee9Sbenno }
122160a32ee9Sbenno cargv[1] = NULL;
122260a32ee9Sbenno } else {
122360a32ee9Sbenno for (i = j = 0; i < wflsz; i++) {
122460a32ee9Sbenno if (!(FLSTAT_TOP_DIR & wfl[i].st.flags))
122560a32ee9Sbenno continue;
122660a32ee9Sbenno assert(S_ISDIR(wfl[i].st.mode));
122760a32ee9Sbenno assert(strcmp(wfl[i].wpath, "."));
1228f1dcb30aSderaadt c = asprintf(&cargv[j], "%s/%s", root, wfl[i].wpath);
122995af8abfSderaadt if (c == -1) {
1230b2a7eac7Sbenno ERR("asprintf");
123160a32ee9Sbenno cargv[j] = NULL;
123260a32ee9Sbenno goto out;
123360a32ee9Sbenno }
1234b2a7eac7Sbenno LOG4("%s: will scan for deletions", cargv[j]);
123560a32ee9Sbenno j++;
123660a32ee9Sbenno }
123760a32ee9Sbenno assert(j == cargvs);
123860a32ee9Sbenno cargv[j] = NULL;
123960a32ee9Sbenno }
124060a32ee9Sbenno
1241b2a7eac7Sbenno LOG2("delete from %zu directories", cargvs);
124260a32ee9Sbenno
124360a32ee9Sbenno /*
124460a32ee9Sbenno * Next, use the standard hcreate(3) hashtable interface to hash
124560a32ee9Sbenno * all of the files that we want to synchronise.
124660a32ee9Sbenno * This way, we'll be able to determine which files we want to
124760a32ee9Sbenno * delete in O(n) time instead of O(n * search) time.
124860a32ee9Sbenno * Plus, we can do the scan in-band and only allocate the files
124960a32ee9Sbenno * we want to delete.
125060a32ee9Sbenno */
125160a32ee9Sbenno
125260a32ee9Sbenno if (!hcreate(wflsz)) {
1253b2a7eac7Sbenno ERR("hcreate");
125460a32ee9Sbenno goto out;
125560a32ee9Sbenno }
125660a32ee9Sbenno
125760a32ee9Sbenno for (i = 0; i < wflsz; i++) {
125860a32ee9Sbenno memset(&hent, 0, sizeof(ENTRY));
1259f1dcb30aSderaadt if ((hent.key = strdup(wfl[i].wpath)) == NULL) {
1260b2a7eac7Sbenno ERR("strdup");
126160a32ee9Sbenno goto out;
126260a32ee9Sbenno }
1263f1dcb30aSderaadt if ((hentp = hsearch(hent, ENTER)) == NULL) {
1264b2a7eac7Sbenno ERR("hsearch");
126560a32ee9Sbenno goto out;
126660a32ee9Sbenno } else if (hentp->key != hent.key) {
1267b2a7eac7Sbenno ERRX("%s: duplicate", wfl[i].wpath);
126860a32ee9Sbenno free(hent.key);
126960a32ee9Sbenno goto out;
127060a32ee9Sbenno }
127160a32ee9Sbenno }
127260a32ee9Sbenno
127360a32ee9Sbenno /*
127460a32ee9Sbenno * Now we're going to try to descend into all of the top-level
127560a32ee9Sbenno * directories stipulated by the file list.
127660a32ee9Sbenno * If the directories don't exist, it's ok.
127760a32ee9Sbenno */
127860a32ee9Sbenno
1279f1dcb30aSderaadt if ((fts = fts_open(cargv, FTS_PHYSICAL, NULL)) == NULL) {
1280b2a7eac7Sbenno ERR("fts_open");
128160a32ee9Sbenno goto out;
128260a32ee9Sbenno }
128360a32ee9Sbenno
128460a32ee9Sbenno stripdir = strlen(root) + 1;
128560a32ee9Sbenno errno = 0;
1286f1dcb30aSderaadt while ((ent = fts_read(fts)) != NULL) {
1287f1dcb30aSderaadt if (ent->fts_info == FTS_NS)
128860a32ee9Sbenno continue;
128960a32ee9Sbenno if (!flist_fts_check(sess, ent)) {
129060a32ee9Sbenno errno = 0;
129160a32ee9Sbenno continue;
129260a32ee9Sbenno } else if (stripdir >= ent->fts_pathlen)
129360a32ee9Sbenno continue;
129460a32ee9Sbenno
12951c3d4160Sbket assert(ent->fts_statp != NULL);
12961c3d4160Sbket
12971c3d4160Sbket /*
12981c3d4160Sbket * If rsync is told to avoid crossing a filesystem
12991c3d4160Sbket * boundary when recursing, then exclude all entries
13001c3d4160Sbket * from the list with a device inode, which does not
13011c3d4160Sbket * match that of one of the top-level directories.
13021c3d4160Sbket */
13031c3d4160Sbket
13041c3d4160Sbket if (sess->opts->one_file_system) {
13051c3d4160Sbket flag = 0;
13061c3d4160Sbket for (i = 0; i < wflsz; i++) {
13071c3d4160Sbket if (stat(wfl[i].path, &st) == -1) {
1308b2a7eac7Sbenno ERR("%s: stat", wfl[i].path);
13091c3d4160Sbket goto out;
13101c3d4160Sbket }
13111c3d4160Sbket if (ent->fts_statp->st_dev == st.st_dev) {
13121c3d4160Sbket flag = 1;
13131c3d4160Sbket break;
13141c3d4160Sbket }
13151c3d4160Sbket }
13161c3d4160Sbket if (!flag)
13171c3d4160Sbket continue;
13181c3d4160Sbket }
13191c3d4160Sbket
132057987d16Sclaudio /* filter files on delete */
132157987d16Sclaudio /* TODO handle --delete-excluded */
132257987d16Sclaudio if (rules_match(ent->fts_path + stripdir,
132357987d16Sclaudio (ent->fts_info == FTS_D)) == -1) {
132457987d16Sclaudio WARNX("skip excluded file %s",
132557987d16Sclaudio ent->fts_path + stripdir);
132657987d16Sclaudio fts_set(fts, ent, FTS_SKIP);
132757987d16Sclaudio continue;
132857987d16Sclaudio }
132957987d16Sclaudio
133060a32ee9Sbenno /* Look up in hashtable. */
133160a32ee9Sbenno
133260a32ee9Sbenno memset(&hent, 0, sizeof(ENTRY));
133360a32ee9Sbenno hent.key = ent->fts_path + stripdir;
1334f1dcb30aSderaadt if (hsearch(hent, FIND) != NULL)
133560a32ee9Sbenno continue;
133660a32ee9Sbenno
133760a32ee9Sbenno /* Not found: we'll delete it. */
133860a32ee9Sbenno
1339ba617adaSbenno if (!flist_realloc(fl, sz, &max)) {
1340b2a7eac7Sbenno ERRX1("flist_realloc");
134160a32ee9Sbenno goto out;
134260a32ee9Sbenno }
134360a32ee9Sbenno f = &(*fl)[*sz - 1];
134460a32ee9Sbenno
1345f1dcb30aSderaadt if ((f->path = strdup(ent->fts_path)) == NULL) {
1346b2a7eac7Sbenno ERR("strdup");
134760a32ee9Sbenno goto out;
134860a32ee9Sbenno }
134960a32ee9Sbenno f->wpath = f->path + stripdir;
135060a32ee9Sbenno flist_copy_stat(f, ent->fts_statp);
135160a32ee9Sbenno errno = 0;
135260a32ee9Sbenno }
135360a32ee9Sbenno
135460a32ee9Sbenno if (errno) {
1355b2a7eac7Sbenno ERR("fts_read");
135660a32ee9Sbenno goto out;
135760a32ee9Sbenno }
135860a32ee9Sbenno
135960a32ee9Sbenno qsort(*fl, *sz, sizeof(struct flist), flist_cmp);
136060a32ee9Sbenno rc = 1;
136160a32ee9Sbenno out:
1362f1dcb30aSderaadt if (fts != NULL)
136360a32ee9Sbenno fts_close(fts);
136460a32ee9Sbenno for (i = 0; i < cargvs; i++)
136560a32ee9Sbenno free(cargv[i]);
136660a32ee9Sbenno free(cargv);
136760a32ee9Sbenno hdestroy();
136860a32ee9Sbenno return rc;
136960a32ee9Sbenno }
137060a32ee9Sbenno
137160a32ee9Sbenno /*
137260a32ee9Sbenno * Delete all files and directories in "fl".
137360a32ee9Sbenno * If called with a zero-length "fl", does nothing.
137460a32ee9Sbenno * If dry_run is specified, simply write what would be done.
137560a32ee9Sbenno * Return zero on failure, non-zero on success.
137660a32ee9Sbenno */
137760a32ee9Sbenno int
flist_del(struct sess * sess,int root,const struct flist * fl,size_t flsz)1378df3fb795Sbenno flist_del(struct sess *sess, int root, const struct flist *fl, size_t flsz)
137960a32ee9Sbenno {
138060a32ee9Sbenno ssize_t i;
138160a32ee9Sbenno int flag;
138260a32ee9Sbenno
1383f1dcb30aSderaadt if (flsz == 0)
138460a32ee9Sbenno return 1;
138560a32ee9Sbenno
138660a32ee9Sbenno assert(sess->opts->del);
138760a32ee9Sbenno assert(sess->opts->recursive);
138860a32ee9Sbenno
138960a32ee9Sbenno for (i = flsz - 1; i >= 0; i--) {
1390b2a7eac7Sbenno LOG1("%s: deleting", fl[i].wpath);
139160a32ee9Sbenno if (sess->opts->dry_run)
139260a32ee9Sbenno continue;
1393f1dcb30aSderaadt assert(root != -1);
139460a32ee9Sbenno flag = S_ISDIR(fl[i].st.mode) ? AT_REMOVEDIR : 0;
1395f1dcb30aSderaadt if (unlinkat(root, fl[i].wpath, flag) == -1 &&
1396f1dcb30aSderaadt errno != ENOENT) {
1397b2a7eac7Sbenno ERR("%s: unlinkat", fl[i].wpath);
139860a32ee9Sbenno return 0;
139960a32ee9Sbenno }
140060a32ee9Sbenno }
140160a32ee9Sbenno
140260a32ee9Sbenno return 1;
140360a32ee9Sbenno }
1404