xref: /openbsd-src/usr.bin/rsync/flist.c (revision 5fe4a96f81734a61d2d5b538ec319f1884412c58)
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