/*
 * Routines to authenticate access to a daemon (hosts allow/deny).
 *
 * Copyright (C) 1998 Andrew Tridgell
 * Copyright (C) 2004-2022 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"
#include "ifuncs.h"
#ifdef HAVE_NETGROUP_H
#include <netgroup.h>
#endif

static int allow_forward_dns;

extern const char undetermined_hostname[];

static int match_hostname(const char **host_ptr, const char *addr, const char *tok)
{
	struct hostent *hp;
	unsigned int i;
	const char *host = *host_ptr;

	if (!host || !*host)
		return 0;

#ifdef HAVE_INNETGR
	if (*tok == '@' && tok[1])
		return innetgr(tok + 1, host, NULL, NULL);
#endif

	/* First check if the reverse-DNS-determined hostname matches. */
	if (iwildmatch(tok, host))
		return 1;

	if (!allow_forward_dns)
		return 0;

	/* Fail quietly if tok is an address or wildcarded entry, not a simple hostname. */
	if (!tok[strspn(tok, ".0123456789")] || tok[strcspn(tok, ":/*?[")])
		return 0;

	/* Now try forward-DNS on the token (config-specified hostname) and see if the IP matches. */
	if (!(hp = gethostbyname(tok)))
		return 0;

	for (i = 0; hp->h_addr_list[i] != NULL; i++) {
		if (strcmp(addr, inet_ntoa(*(struct in_addr*)(hp->h_addr_list[i]))) == 0) {
			/* If reverse lookups are off, we'll use the conf-specified
			 * hostname in preference to UNDETERMINED. */
			if (host == undetermined_hostname)
				*host_ptr = strdup(tok);
			return 1;
		}
	}

	return 0;
}

static int match_binary(const char *b1, const char *b2, const char *mask, int addrlen)
{
	int i;

	for (i = 0; i < addrlen; i++) {
		if ((b1[i] ^ b2[i]) & mask[i])
			return 0;
	}

	return 1;
}

static void make_mask(char *mask, int plen, int addrlen)
{
	int w, b;

	w = plen >> 3;
	b = plen & 0x7;

	if (w)
		memset(mask, 0xff, w);
	if (w < addrlen)
		mask[w] = 0xff & (0xff<<(8-b));
	if (w+1 < addrlen)
		memset(mask+w+1, 0, addrlen-w-1);

	return;
}

static int match_address(const char *addr, const char *tok)
{
	char *p;
	struct addrinfo hints, *resa, *rest;
	int gai;
	int ret = 0;
	int addrlen = 0;
#ifdef HAVE_STRTOL
	long int bits;
#else
	int bits;
#endif
	char mask[16];
	char *a = NULL, *t = NULL;

	if (!addr || !*addr)
		return 0;

	p = strchr(tok,'/');
	if (p)
		*p = '\0';

	/* Fail quietly if tok is a hostname, not an address. */
	if (tok[strspn(tok, ".0123456789")] && strchr(tok, ':') == NULL) {
		if (p)
			*p = '/';
		return 0;
	}

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = PF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
#ifdef AI_NUMERICHOST
	hints.ai_flags = AI_NUMERICHOST;
#endif

	if (getaddrinfo(addr, NULL, &hints, &resa) != 0) {
		if (p)
			*p = '/';
		return 0;
	}

	gai = getaddrinfo(tok, NULL, &hints, &rest);
	if (p)
		*p++ = '/';
	if (gai != 0) {
		rprintf(FLOG, "error matching address %s: %s\n",
			tok, gai_strerror(gai));
		freeaddrinfo(resa);
		return 0;
	}

	if (rest->ai_family != resa->ai_family) {
		ret = 0;
		goto out;
	}

	switch(resa->ai_family) {
	case PF_INET:
		a = (char *)&((struct sockaddr_in *)resa->ai_addr)->sin_addr;
		t = (char *)&((struct sockaddr_in *)rest->ai_addr)->sin_addr;
		addrlen = 4;

		break;

#ifdef INET6
	case PF_INET6: {
		struct sockaddr_in6 *sin6a, *sin6t;

		sin6a = (struct sockaddr_in6 *)resa->ai_addr;
		sin6t = (struct sockaddr_in6 *)rest->ai_addr;

		a = (char *)&sin6a->sin6_addr;
		t = (char *)&sin6t->sin6_addr;

		addrlen = 16;

#ifdef HAVE_SOCKADDR_IN6_SCOPE_ID
		if (sin6t->sin6_scope_id && sin6a->sin6_scope_id != sin6t->sin6_scope_id) {
			ret = 0;
			goto out;
		}
#endif

		break;
	}
#endif
	default:
		rprintf(FLOG, "unknown family %u\n", rest->ai_family);
		ret = 0;
		goto out;
	}

	bits = -1;
	if (p) {
		if (inet_pton(resa->ai_addr->sa_family, p, mask) <= 0) {
#ifdef HAVE_STRTOL
			char *ep = NULL;
#else
			unsigned char *pp;
#endif

#ifdef HAVE_STRTOL
			bits = strtol(p, &ep, 10);
			if (!*p || *ep) {
				rprintf(FLOG, "malformed mask in %s\n", tok);
				ret = 0;
				goto out;
			}
#else
			for (pp = (unsigned char *)p; *pp; pp++) {
				if (!isascii(*pp) || !isdigit(*pp)) {
					rprintf(FLOG, "malformed mask in %s\n", tok);
					ret = 0;
					goto out;
				}
			}
			bits = atoi(p);
#endif
			if (bits == 0) {
				ret = 1;
				goto out;
			}
			if (bits < 0 || bits > (addrlen << 3)) {
				rprintf(FLOG, "malformed mask in %s\n", tok);
				ret = 0;
				goto out;
			}
		}
	} else {
		bits = 128;
	}

	if (bits >= 0)
		make_mask(mask, bits, addrlen);

	ret = match_binary(a, t, mask, addrlen);

  out:
	freeaddrinfo(resa);
	freeaddrinfo(rest);
	return ret;
}

static int access_match(const char *list, const char *addr, const char **host_ptr)
{
	char *tok;
	char *list2 = strdup(list);

	strlower(list2);

	for (tok = strtok(list2, " ,\t"); tok; tok = strtok(NULL, " ,\t")) {
		if (match_hostname(host_ptr, addr, tok) || match_address(addr, tok)) {
			free(list2);
			return 1;
		}
	}

	free(list2);
	return 0;
}

int allow_access(const char *addr, const char **host_ptr, int i)
{
	const char *allow_list = lp_hosts_allow(i);
	const char *deny_list = lp_hosts_deny(i);

	if (allow_list && !*allow_list)
		allow_list = NULL;
	if (deny_list && !*deny_list)
		deny_list = NULL;

	allow_forward_dns = lp_forward_lookup(i);

	/* If we match an allow-list item, we always allow access. */
	if (allow_list) {
		if (access_match(allow_list, addr, host_ptr))
			return 1;
		/* For an allow-list w/o a deny-list, disallow non-matches. */
		if (!deny_list)
			return 0;
	}

	/* If we match a deny-list item (and got past any allow-list
	 * items), we always disallow access. */
	if (deny_list && access_match(deny_list, addr, host_ptr))
		return 0;

	/* Allow all other access. */
	return 1;
}
/*
 * Handle passing Access Control Lists between systems.
 *
 * Copyright (C) 1996 Andrew Tridgell
 * Copyright (C) 1996 Paul Mackerras
 * Copyright (C) 2006-2022 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"
#include "lib/sysacls.h"

#ifdef SUPPORT_ACLS

extern int dry_run;
extern int am_root;
extern int read_only;
extern int list_only;
extern mode_t orig_umask;
extern int numeric_ids;
extern int inc_recurse;
extern int preserve_devices;
extern int preserve_specials;

/* Flags used to indicate what items are being transmitted for an entry. */
#define XMIT_USER_OBJ (1<<0)
#define XMIT_GROUP_OBJ (1<<1)
#define XMIT_MASK_OBJ (1<<2)
#define XMIT_OTHER_OBJ (1<<3)
#define XMIT_NAME_LIST (1<<4)

#define NO_ENTRY ((uchar)0x80) /* Default value of a NON-name-list entry. */

#define NAME_IS_USER (1u<<31) /* Bit used only on a name-list entry. */

/* When we send the access bits over the wire, we shift them 2 bits to the
 * left and use the lower 2 bits as flags (relevant only to a name entry).
 * This makes the protocol more efficient than sending a value that would
 * be likely to have its highest bits set. */
#define XFLAG_NAME_FOLLOWS 0x0001u
#define XFLAG_NAME_IS_USER 0x0002u

/* === ACL structures === */

typedef struct {
	id_t id;
	uint32 access;
} id_access;

typedef struct {
	id_access *idas;
	int count;
} ida_entries;

typedef struct {
	char *name;
	uchar len;
} idname;

typedef struct rsync_acl {
	ida_entries names;
	/* These will be NO_ENTRY if there's no such entry. */
	uchar user_obj;
	uchar group_obj;
	uchar mask_obj;
	uchar other_obj;
} rsync_acl;

typedef struct {
	rsync_acl racl;
	SMB_ACL_T sacl;
} acl_duo;

static const rsync_acl empty_rsync_acl = {
	{NULL, 0}, NO_ENTRY, NO_ENTRY, NO_ENTRY, NO_ENTRY
};

static item_list access_acl_list = EMPTY_ITEM_LIST;
static item_list default_acl_list = EMPTY_ITEM_LIST;

static size_t prior_access_count = (size_t)-1;
static size_t prior_default_count = (size_t)-1;

/* === Calculations on ACL types === */

static const char *str_acl_type(SMB_ACL_TYPE_T type)
{
	switch (type) {
	case SMB_ACL_TYPE_ACCESS:
#ifdef HAVE_OSX_ACLS
		return "ACL_TYPE_EXTENDED";
#else
		return "ACL_TYPE_ACCESS";
#endif
	case SMB_ACL_TYPE_DEFAULT:
		return "ACL_TYPE_DEFAULT";
	default:
		break;
	}
	return "unknown ACL type!";
}

static int calc_sacl_entries(const rsync_acl *racl)
{
	/* A System ACL always gets user/group/other permission entries. */
	return racl->names.count
#ifdef ACLS_NEED_MASK
	     + 1
#else
	     + (racl->mask_obj != NO_ENTRY)
#endif
	     + 3;
}

/* Extracts and returns the permission bits from the ACL.  This cannot be
 * called on an rsync_acl that has NO_ENTRY in any spot but the mask. */
static int rsync_acl_get_perms(const rsync_acl *racl)
{
	return (racl->user_obj << 6)
	     + ((racl->mask_obj != NO_ENTRY ? racl->mask_obj : racl->group_obj) << 3)
	     + racl->other_obj;
}

/* Removes the permission-bit entries from the ACL because these
 * can be reconstructed from the file's mode. */
static void rsync_acl_strip_perms(stat_x *sxp)
{
	rsync_acl *racl = sxp->acc_acl;

	racl->user_obj = NO_ENTRY;
	if (racl->mask_obj == NO_ENTRY)
		racl->group_obj = NO_ENTRY;
	else {
		int group_perms = (sxp->st.st_mode >> 3) & 7;
		if (racl->group_obj == group_perms)
			racl->group_obj = NO_ENTRY;
#ifndef HAVE_SOLARIS_ACLS
		if (racl->names.count != 0 && racl->mask_obj == group_perms)
			racl->mask_obj = NO_ENTRY;
#endif
	}
	racl->other_obj = NO_ENTRY;
}

/* Given an empty rsync_acl, fake up the permission bits. */
static void rsync_acl_fake_perms(rsync_acl *racl, mode_t mode)
{
	racl->user_obj = (mode >> 6) & 7;
	racl->group_obj = (mode >> 3) & 7;
	racl->other_obj = mode & 7;
}

/* === Rsync ACL functions === */

static rsync_acl *create_racl(void)
{
	rsync_acl *racl = new(rsync_acl);

	*racl = empty_rsync_acl;

	return racl;
}

static BOOL ida_entries_equal(const ida_entries *ial1, const ida_entries *ial2)
{
	id_access *ida1, *ida2;
	int count = ial1->count;
	if (count != ial2->count)
		return False;
	ida1 = ial1->idas;
	ida2 = ial2->idas;
	for (; count--; ida1++, ida2++) {
		if (ida1->access != ida2->access || ida1->id != ida2->id)
			return False;
	}
	return True;
}

static BOOL rsync_acl_equal(const rsync_acl *racl1, const rsync_acl *racl2)
{
	return racl1->user_obj == racl2->user_obj
	    && racl1->group_obj == racl2->group_obj
	    && racl1->mask_obj == racl2->mask_obj
	    && racl1->other_obj == racl2->other_obj
	    && ida_entries_equal(&racl1->names, &racl2->names);
}

/* Are the extended (non-permission-bit) entries equal?  If so, the rest of
 * the ACL will be handled by the normal mode-preservation code.  This is
 * only meaningful for access ACLs!  Note: the 1st arg is a fully-populated
 * rsync_acl, but the 2nd parameter can be a condensed rsync_acl, which means
 * that it might have several of its permission objects set to NO_ENTRY. */
static BOOL rsync_acl_equal_enough(const rsync_acl *racl1,
				   const rsync_acl *racl2, mode_t m)
{
	if ((racl1->mask_obj ^ racl2->mask_obj) & NO_ENTRY)
		return False; /* One has a mask and the other doesn't */

	/* When there's a mask, the group_obj becomes an extended entry. */
	if (racl1->mask_obj != NO_ENTRY) {
		/* A condensed rsync_acl with a mask can only have no
		 * group_obj when it was identical to the mask.  This
		 * means that it was also identical to the group attrs
		 * from the mode. */
		if (racl2->group_obj == NO_ENTRY) {
			if (racl1->group_obj != ((m >> 3) & 7))
				return False;
		} else if (racl1->group_obj != racl2->group_obj)
			return False;
	}
	return ida_entries_equal(&racl1->names, &racl2->names);
}

static void rsync_acl_free(rsync_acl *racl)
{
	if (racl->names.idas)
		free(racl->names.idas);
	*racl = empty_rsync_acl;
}

void free_acl(stat_x *sxp)
{
	if (sxp->acc_acl) {
		rsync_acl_free(sxp->acc_acl);
		free(sxp->acc_acl);
		sxp->acc_acl = NULL;
	}
	if (sxp->def_acl) {
		rsync_acl_free(sxp->def_acl);
		free(sxp->def_acl);
		sxp->def_acl = NULL;
	}
}

#ifdef SMB_ACL_NEED_SORT
static int id_access_sorter(const void *r1, const void *r2)
{
	id_access *ida1 = (id_access *)r1;
	id_access *ida2 = (id_access *)r2;
	id_t rid1 = ida1->id, rid2 = ida2->id;
	if ((ida1->access ^ ida2->access) & NAME_IS_USER)
		return ida1->access & NAME_IS_USER ? -1 : 1;
	return rid1 == rid2 ? 0 : rid1 < rid2 ? -1 : 1;
}
#endif

/* === System ACLs === */

/* Unpack system ACL -> rsync ACL verbatim.  Return whether we succeeded. */
static BOOL unpack_smb_acl(SMB_ACL_T sacl, rsync_acl *racl)
{
	static item_list temp_ida_list = EMPTY_ITEM_LIST;
	SMB_ACL_ENTRY_T entry;
	const char *errfun;
	int rc;

	errfun = "sys_acl_get_entry";
	for (rc = sys_acl_get_entry(sacl, SMB_ACL_FIRST_ENTRY, &entry);
	     rc == 1;
	     rc = sys_acl_get_entry(sacl, SMB_ACL_NEXT_ENTRY, &entry)) {
		SMB_ACL_TAG_T tag_type;
		uint32 access;
		id_t g_u_id;
		id_access *ida;
		if ((rc = sys_acl_get_info(entry, &tag_type, &access, &g_u_id)) != 0) {
			errfun = "sys_acl_get_info";
			break;
		}
		/* continue == done with entry; break == store in temporary ida list */
		switch (tag_type) {
#ifndef HAVE_OSX_ACLS
		case SMB_ACL_USER_OBJ:
			if (racl->user_obj == NO_ENTRY)
				racl->user_obj = access;
			else
				rprintf(FINFO, "unpack_smb_acl: warning: duplicate USER_OBJ entry ignored\n");
			continue;
		case SMB_ACL_GROUP_OBJ:
			if (racl->group_obj == NO_ENTRY)
				racl->group_obj = access;
			else
				rprintf(FINFO, "unpack_smb_acl: warning: duplicate GROUP_OBJ entry ignored\n");
			continue;
		case SMB_ACL_MASK:
			if (racl->mask_obj == NO_ENTRY)
				racl->mask_obj = access;
			else
				rprintf(FINFO, "unpack_smb_acl: warning: duplicate MASK entry ignored\n");
			continue;
		case SMB_ACL_OTHER:
			if (racl->other_obj == NO_ENTRY)
				racl->other_obj = access;
			else
				rprintf(FINFO, "unpack_smb_acl: warning: duplicate OTHER entry ignored\n");
			continue;
#endif
		case SMB_ACL_USER:
			access |= NAME_IS_USER;
			break;
		case SMB_ACL_GROUP:
			break;
		default:
			rprintf(FINFO, "unpack_smb_acl: warning: entry with unrecognized tag type ignored\n");
			continue;
		}
		ida = EXPAND_ITEM_LIST(&temp_ida_list, id_access, -10);
		ida->id = g_u_id;
		ida->access = access;
	}
	if (rc) {
		rsyserr(FERROR_XFER, errno, "unpack_smb_acl: %s()", errfun);
		rsync_acl_free(racl);
		return False;
	}

	/* Transfer the count id_access items out of the temp_ida_list
	 * into the names ida_entries list in racl. */
	if (temp_ida_list.count) {
#ifdef SMB_ACL_NEED_SORT
		if (temp_ida_list.count > 1) {
			qsort(temp_ida_list.items, temp_ida_list.count, sizeof (id_access), id_access_sorter);
		}
#endif
		racl->names.idas = new_array(id_access, temp_ida_list.count);
		memcpy(racl->names.idas, temp_ida_list.items, temp_ida_list.count * sizeof (id_access));
	} else
		racl->names.idas = NULL;

	racl->names.count = temp_ida_list.count;

	/* Truncate the temporary list now that its idas have been saved. */
	temp_ida_list.count = 0;

	return True;
}

/* Synactic sugar for system calls */

#define CALL_OR_ERROR(func,args,str) \
	do { \
		if (func args) { \
			errfun = str; \
			goto error_exit; \
		} \
	} while (0)

#define COE(func,args) CALL_OR_ERROR(func,args,#func)
#define COE2(func,args) CALL_OR_ERROR(func,args,NULL)

#ifndef HAVE_OSX_ACLS
/* Store the permissions in the system ACL entry. */
static int store_access_in_entry(uint32 access, SMB_ACL_ENTRY_T entry)
{
	if (sys_acl_set_access_bits(entry, access)) {
		rsyserr(FERROR_XFER, errno, "store_access_in_entry sys_acl_set_access_bits()");
		return -1;
	}
	return 0;
}
#endif

/* Pack rsync ACL -> system ACL verbatim.  Return whether we succeeded. */
static BOOL pack_smb_acl(SMB_ACL_T *smb_acl, const rsync_acl *racl)
{
#ifdef ACLS_NEED_MASK
	uchar mask_bits;
#endif
	size_t count;
	id_access *ida;
	const char *errfun = NULL;
	SMB_ACL_ENTRY_T entry;

	if (!(*smb_acl = sys_acl_init(calc_sacl_entries(racl)))) {
		rsyserr(FERROR_XFER, errno, "pack_smb_acl: sys_acl_init()");
		return False;
	}

#ifndef HAVE_OSX_ACLS
	COE( sys_acl_create_entry,(smb_acl, &entry) );
	COE( sys_acl_set_info,(entry, SMB_ACL_USER_OBJ, racl->user_obj & ~NO_ENTRY, 0) );
#endif

	for (ida = racl->names.idas, count = racl->names.count; count; ida++, count--) {
#ifdef SMB_ACL_NEED_SORT
		if (!(ida->access & NAME_IS_USER))
			break;
#endif
		COE( sys_acl_create_entry,(smb_acl, &entry) );
		COE( sys_acl_set_info,
		    (entry,
		     ida->access & NAME_IS_USER ? SMB_ACL_USER : SMB_ACL_GROUP,
		     ida->access & ~NAME_IS_USER, ida->id) );
	}

#ifndef HAVE_OSX_ACLS
	COE( sys_acl_create_entry,(smb_acl, &entry) );
	COE( sys_acl_set_info,(entry, SMB_ACL_GROUP_OBJ, racl->group_obj & ~NO_ENTRY, 0) );

#ifdef SMB_ACL_NEED_SORT
	for ( ; count; ida++, count--) {
		COE( sys_acl_create_entry,(smb_acl, &entry) );
		COE( sys_acl_set_info,(entry, SMB_ACL_GROUP, ida->access, ida->id) );
	}
#endif

#ifdef ACLS_NEED_MASK
	mask_bits = racl->mask_obj == NO_ENTRY ? racl->group_obj & ~NO_ENTRY : racl->mask_obj;
	COE( sys_acl_create_entry,(smb_acl, &entry) );
	COE( sys_acl_set_info,(entry, SMB_ACL_MASK, mask_bits, 0) );
#else
	if (racl->mask_obj != NO_ENTRY) {
		COE( sys_acl_create_entry,(smb_acl, &entry) );
		COE( sys_acl_set_info,(entry, SMB_ACL_MASK, racl->mask_obj, 0) );
	}
#endif

	COE( sys_acl_create_entry,(smb_acl, &entry) );
	COE( sys_acl_set_info,(entry, SMB_ACL_OTHER, racl->other_obj & ~NO_ENTRY, 0) );
#endif

#ifdef DEBUG
	if (sys_acl_valid(*smb_acl) < 0)
		rprintf(FERROR_XFER, "pack_smb_acl: warning: system says the ACL I packed is invalid\n");
#endif

	return True;

  error_exit:
	if (errfun) {
		rsyserr(FERROR_XFER, errno, "pack_smb_acl %s()", errfun);
	}
	sys_acl_free_acl(*smb_acl);
	return False;
}

static int find_matching_rsync_acl(const rsync_acl *racl, SMB_ACL_TYPE_T type,
				   const item_list *racl_list)
{
	static int access_match = -1, default_match = -1;
	int *match = type == SMB_ACL_TYPE_ACCESS ? &access_match : &default_match;
	size_t count = racl_list->count;

	/* If this is the first time through or we didn't match the last
	 * time, then start at the end of the list, which should be the
	 * best place to start hunting. */
	if (*match == -1)
		*match = racl_list->count - 1;
	while (count--) {
		rsync_acl *base = racl_list->items;
		if (rsync_acl_equal(base + *match, racl))
			return *match;
		if (!(*match)--)
			*match = racl_list->count - 1;
	}

	*match = -1;
	return *match;
}

static int get_rsync_acl(const char *fname, rsync_acl *racl,
			 SMB_ACL_TYPE_T type, mode_t mode)
{
	SMB_ACL_T sacl;

#ifdef SUPPORT_XATTRS
	/* --fake-super support: load ACLs from an xattr. */
	if (am_root < 0) {
		char *buf;
		size_t len;
		int cnt;

		if ((buf = get_xattr_acl(fname, type == SMB_ACL_TYPE_ACCESS, &len)) == NULL)
			return 0;
		cnt = (len - 4*4) / (4+4);
		if (len < 4*4 || len != (size_t)cnt*(4+4) + 4*4) {
			free(buf);
			return -1;
		}

		racl->user_obj = IVAL(buf, 0);
		if (racl->user_obj == NO_ENTRY)
			racl->user_obj = (mode >> 6) & 7;
		racl->group_obj = IVAL(buf, 4);
		if (racl->group_obj == NO_ENTRY)
			racl->group_obj = (mode >> 3) & 7;
		racl->mask_obj = IVAL(buf, 8);
		racl->other_obj = IVAL(buf, 12);
		if (racl->other_obj == NO_ENTRY)
			racl->other_obj = mode & 7;

		if (cnt) {
			char *bp = buf + 4*4;
			id_access *ida = racl->names.idas = new_array(id_access, cnt);
			racl->names.count = cnt;
			for ( ; cnt--; ida++, bp += 4+4) {
				ida->id = IVAL(bp, 0);
				ida->access = IVAL(bp, 4);
			}
		}
		free(buf);
		return 0;
	}
#endif

	if ((sacl = sys_acl_get_file(fname, type)) != 0) {
		BOOL ok = unpack_smb_acl(sacl, racl);

		sys_acl_free_acl(sacl);
		if (!ok) {
			rsyserr(FERROR_XFER, errno, "get_acl: unpack_smb_acl(%s)", fname);
			return -1;
		}
	} else if (no_acl_syscall_error(errno)) {
		/* ACLs are not supported, so pretend we have a basic ACL. */
		if (type == SMB_ACL_TYPE_ACCESS)
			rsync_acl_fake_perms(racl, mode);
	} else {
		rsyserr(FERROR_XFER, errno, "get_acl: sys_acl_get_file(%s, %s)",
			fname, str_acl_type(type));
		return -1;
	}

	return 0;
}

/* Return the Access Control List for the given filename. */
int get_acl(const char *fname, stat_x *sxp)
{
	sxp->acc_acl = create_racl();

	if (S_ISREG(sxp->st.st_mode) || S_ISDIR(sxp->st.st_mode)) {
		/* Everyone supports this. */
	} else if (S_ISLNK(sxp->st.st_mode)) {
		return 0;
	} else if (IS_SPECIAL(sxp->st.st_mode)) {
#ifndef NO_SPECIAL_ACLS
		if (!preserve_specials)
#endif
			return 0;
	} else if (IS_DEVICE(sxp->st.st_mode)) {
#ifndef NO_DEVICE_ACLS
		if (!preserve_devices)
#endif
			return 0;
	} else if (IS_MISSING_FILE(sxp->st))
		return 0;

	if (get_rsync_acl(fname, sxp->acc_acl, SMB_ACL_TYPE_ACCESS,
			  sxp->st.st_mode) < 0) {
		free_acl(sxp);
		return -1;
	}

	if (S_ISDIR(sxp->st.st_mode)) {
		sxp->def_acl = create_racl();
		if (get_rsync_acl(fname, sxp->def_acl, SMB_ACL_TYPE_DEFAULT,
				  sxp->st.st_mode) < 0) {
			free_acl(sxp);
			return -1;
		}
	}

	return 0;
}

/* === Send functions === */

/* Send the ida list over the file descriptor. */
static void send_ida_entries(int f, const ida_entries *idal)
{
	id_access *ida;
	size_t count = idal->count;

	write_varint(f, idal->count);

	for (ida = idal->idas; count--; ida++) {
		uint32 xbits = ida->access << 2;
		const char *name;
		if (ida->access & NAME_IS_USER) {
			xbits |= XFLAG_NAME_IS_USER;
			name = numeric_ids ? NULL : add_uid(ida->id);
		} else
			name = numeric_ids ? NULL : add_gid(ida->id);
		write_varint(f, ida->id);
		if (inc_recurse && name) {
			int len = strlen(name);
			write_varint(f, xbits | XFLAG_NAME_FOLLOWS);
			write_byte(f, len);
			write_buf(f, name, len);
		} else
			write_varint(f, xbits);
	}
}

static void send_rsync_acl(int f, rsync_acl *racl, SMB_ACL_TYPE_T type,
			   item_list *racl_list)
{
	int ndx = find_matching_rsync_acl(racl, type, racl_list);

	/* Send 0 (-1 + 1) to indicate that literal ACL data follows. */
	write_varint(f, ndx + 1);

	if (ndx < 0) {
		rsync_acl *new_racl = EXPAND_ITEM_LIST(racl_list, rsync_acl, 1000);
		uchar flags = 0;

		if (racl->user_obj != NO_ENTRY)
			flags |= XMIT_USER_OBJ;
		if (racl->group_obj != NO_ENTRY)
			flags |= XMIT_GROUP_OBJ;
		if (racl->mask_obj != NO_ENTRY)
			flags |= XMIT_MASK_OBJ;
		if (racl->other_obj != NO_ENTRY)
			flags |= XMIT_OTHER_OBJ;
		if (racl->names.count)
			flags |= XMIT_NAME_LIST;

		write_byte(f, flags);

		if (flags & XMIT_USER_OBJ)
			write_varint(f, racl->user_obj);
		if (flags & XMIT_GROUP_OBJ)
			write_varint(f, racl->group_obj);
		if (flags & XMIT_MASK_OBJ)
			write_varint(f, racl->mask_obj);
		if (flags & XMIT_OTHER_OBJ)
			write_varint(f, racl->other_obj);
		if (flags & XMIT_NAME_LIST)
			send_ida_entries(f, &racl->names);

		/* Give the allocated data to the new list object. */
		*new_racl = *racl;
		*racl = empty_rsync_acl;
	}
}

/* Send the ACL from the stat_x structure down the indicated file descriptor.
 * This also frees the ACL data. */
void send_acl(int f, stat_x *sxp)
{
	if (!sxp->acc_acl) {
		sxp->acc_acl = create_racl();
		rsync_acl_fake_perms(sxp->acc_acl, sxp->st.st_mode);
	}
	/* Avoid sending values that can be inferred from other data. */
	rsync_acl_strip_perms(sxp);

	send_rsync_acl(f, sxp->acc_acl, SMB_ACL_TYPE_ACCESS, &access_acl_list);

	if (S_ISDIR(sxp->st.st_mode)) {
		if (!sxp->def_acl)
			sxp->def_acl = create_racl();

		send_rsync_acl(f, sxp->def_acl, SMB_ACL_TYPE_DEFAULT, &default_acl_list);
	}
}

/* === Receive functions === */

static uint32 recv_acl_access(int f, uchar *name_follows_ptr)
{
	uint32 access = read_varint(f);

	if (name_follows_ptr) {
		int flags = access & 3;
		access >>= 2;
		if (am_root >= 0 && access & ~SMB_ACL_VALID_NAME_BITS)
			goto value_error;
		if (flags & XFLAG_NAME_FOLLOWS)
			*name_follows_ptr = 1;
		else
			*name_follows_ptr = 0;
		if (flags & XFLAG_NAME_IS_USER)
			access |= NAME_IS_USER;
	} else if (am_root >= 0 && access & ~SMB_ACL_VALID_OBJ_BITS) {
	  value_error:
		rprintf(FERROR_XFER, "recv_acl_access: value out of range: %x\n",
			access);
		exit_cleanup(RERR_STREAMIO);
	}

	return access;
}

static uchar recv_ida_entries(int f, ida_entries *ent)
{
	uchar computed_mask_bits = 0;
	int i, count = read_varint(f);

	ent->idas = count ? new_array(id_access, count) : NULL;
	ent->count = count;

	for (i = 0; i < count; i++) {
		uchar has_name;
		id_t id = read_varint(f);
		uint32 access = recv_acl_access(f, &has_name);

		if (has_name) {
			if (access & NAME_IS_USER)
				id = recv_user_name(f, id);
			else
				id = recv_group_name(f, id, NULL);
		} else if (access & NAME_IS_USER) {
			if (inc_recurse && am_root && !numeric_ids)
				id = match_uid(id);
		} else {
			if (inc_recurse && (!am_root || !numeric_ids))
				id = match_gid(id, NULL);
		}

		ent->idas[i].id = id;
		ent->idas[i].access = access;
		computed_mask_bits |= access;
	}

	return computed_mask_bits & ~NO_ENTRY;
}

static int recv_rsync_acl(int f, item_list *racl_list, SMB_ACL_TYPE_T type, mode_t mode)
{
	uchar computed_mask_bits = 0;
	acl_duo *duo_item;
	uchar flags;
	int ndx = read_varint(f);

	if (ndx < 0 || (size_t)ndx > racl_list->count) {
		rprintf(FERROR_XFER, "recv_acl_index: %s ACL index %d > %d\n",
			str_acl_type(type), ndx, (int)racl_list->count);
		exit_cleanup(RERR_STREAMIO);
	}

	if (ndx != 0)
		return ndx - 1;

	ndx = racl_list->count;
	duo_item = EXPAND_ITEM_LIST(racl_list, acl_duo, 1000);
	duo_item->racl = empty_rsync_acl;

	flags = read_byte(f);

	if (flags & XMIT_USER_OBJ)
		duo_item->racl.user_obj = recv_acl_access(f, NULL);
	if (flags & XMIT_GROUP_OBJ)
		duo_item->racl.group_obj = recv_acl_access(f, NULL);
	if (flags & XMIT_MASK_OBJ)
		duo_item->racl.mask_obj = recv_acl_access(f, NULL);
	if (flags & XMIT_OTHER_OBJ)
		duo_item->racl.other_obj = recv_acl_access(f, NULL);
	if (flags & XMIT_NAME_LIST)
		computed_mask_bits |= recv_ida_entries(f, &duo_item->racl.names);

#ifdef HAVE_OSX_ACLS
	/* If we received a superfluous mask, throw it away. */
	duo_item->racl.mask_obj = NO_ENTRY;
	(void)mode;
	(void)computed_mask_bits;
#else
	if (duo_item->racl.names.count && duo_item->racl.mask_obj == NO_ENTRY) {
		/* Mask must be non-empty with lists. */
		if (type == SMB_ACL_TYPE_ACCESS)
			computed_mask_bits = (mode >> 3) & 7;
		else
			computed_mask_bits |= duo_item->racl.group_obj & ~NO_ENTRY;
		duo_item->racl.mask_obj = computed_mask_bits;
	}
#endif

	duo_item->sacl = NULL;

	return ndx;
}

/* Receive the ACL info the sender has included for this file-list entry. */
void receive_acl(int f, struct file_struct *file)
{
	F_ACL(file) = recv_rsync_acl(f, &access_acl_list, SMB_ACL_TYPE_ACCESS, file->mode);

	if (S_ISDIR(file->mode))
		F_DIR_DEFACL(file) = recv_rsync_acl(f, &default_acl_list, SMB_ACL_TYPE_DEFAULT, 0);
}

static int cache_rsync_acl(rsync_acl *racl, SMB_ACL_TYPE_T type, item_list *racl_list)
{
	int ndx;

	if (!racl)
		ndx = -1;
	else if ((ndx = find_matching_rsync_acl(racl, type, racl_list)) == -1) {
		acl_duo *new_duo;
		ndx = racl_list->count;
		new_duo = EXPAND_ITEM_LIST(racl_list, acl_duo, 1000);
		new_duo->racl = *racl;
		new_duo->sacl = NULL;
		*racl = empty_rsync_acl;
	}

	return ndx;
}

/* Turn the ACL data in stat_x into cached ACL data, setting the index
 * values in the file struct. */
void cache_tmp_acl(struct file_struct *file, stat_x *sxp)
{
	if (prior_access_count == (size_t)-1)
		prior_access_count = access_acl_list.count;

	F_ACL(file) = cache_rsync_acl(sxp->acc_acl, SMB_ACL_TYPE_ACCESS, &access_acl_list);

	if (S_ISDIR(sxp->st.st_mode)) {
		if (prior_default_count == (size_t)-1)
			prior_default_count = default_acl_list.count;
		F_DIR_DEFACL(file) = cache_rsync_acl(sxp->def_acl, SMB_ACL_TYPE_DEFAULT, &default_acl_list);
	}
}

static void uncache_duo_acls(item_list *duo_list, size_t start)
{
	acl_duo *duo_item = duo_list->items;
	acl_duo *duo_start = duo_item + start;

	duo_item += duo_list->count;
	duo_list->count = start;

	while (duo_item-- > duo_start) {
		rsync_acl_free(&duo_item->racl);
		if (duo_item->sacl)
			sys_acl_free_acl(duo_item->sacl);
	}
}

void uncache_tmp_acls(void)
{
	if (prior_access_count != (size_t)-1) {
		uncache_duo_acls(&access_acl_list, prior_access_count);
		prior_access_count = (size_t)-1;
	}

	if (prior_default_count != (size_t)-1) {
		uncache_duo_acls(&default_acl_list, prior_default_count);
		prior_default_count = (size_t)-1;
	}
}

#ifndef HAVE_OSX_ACLS
static mode_t change_sacl_perms(SMB_ACL_T sacl, rsync_acl *racl, mode_t old_mode, mode_t mode)
{
	SMB_ACL_ENTRY_T entry;
	const char *errfun;
	int rc;

	if (S_ISDIR(mode)) {
		/* If the sticky bit is going on, it's not safe to allow all
		 * the new ACL to go into effect before it gets set. */
#ifdef SMB_ACL_LOSES_SPECIAL_MODE_BITS
		if (mode & S_ISVTX)
			mode &= ~0077;
#else
		if (mode & S_ISVTX && !(old_mode & S_ISVTX))
			mode &= ~0077;
	} else {
		/* If setuid or setgid is going off, it's not safe to allow all
		 * the new ACL to go into effect before they get cleared. */
		if ((old_mode & S_ISUID && !(mode & S_ISUID))
		 || (old_mode & S_ISGID && !(mode & S_ISGID)))
			mode &= ~0077;
#endif
	}

	errfun = "sys_acl_get_entry";
	for (rc = sys_acl_get_entry(sacl, SMB_ACL_FIRST_ENTRY, &entry);
	     rc == 1;
	     rc = sys_acl_get_entry(sacl, SMB_ACL_NEXT_ENTRY, &entry)) {
		SMB_ACL_TAG_T tag_type;
		if ((rc = sys_acl_get_tag_type(entry, &tag_type)) != 0) {
			errfun = "sys_acl_get_tag_type";
			break;
		}
		switch (tag_type) {
		case SMB_ACL_USER_OBJ:
			COE2( store_access_in_entry,((mode >> 6) & 7, entry) );
			break;
		case SMB_ACL_GROUP_OBJ:
			/* group is only empty when identical to group perms. */
			if (racl->group_obj != NO_ENTRY)
				break;
			COE2( store_access_in_entry,((mode >> 3) & 7, entry) );
			break;
		case SMB_ACL_MASK:
#ifndef HAVE_SOLARIS_ACLS
#ifndef ACLS_NEED_MASK
			/* mask is only empty when we don't need it. */
			if (racl->mask_obj == NO_ENTRY)
				break;
#endif
			COE2( store_access_in_entry,((mode >> 3) & 7, entry) );
#endif
			break;
		case SMB_ACL_OTHER:
			COE2( store_access_in_entry,(mode & 7, entry) );
			break;
		}
	}
	if (rc) {
	  error_exit:
		if (errfun) {
			rsyserr(FERROR_XFER, errno, "change_sacl_perms: %s()",
				errfun);
		}
		return (mode_t)-1;
	}

#ifdef SMB_ACL_LOSES_SPECIAL_MODE_BITS
	/* Ensure that chmod() will be called to restore any lost setid bits. */
	if (old_mode & (S_ISUID | S_ISGID | S_ISVTX)
	 && BITS_EQUAL(old_mode, mode, CHMOD_BITS))
		old_mode &= ~(S_ISUID | S_ISGID | S_ISVTX);
#endif

	/* Return the mode of the file on disk, as we will set them. */
	return (old_mode & ~ACCESSPERMS) | (mode & ACCESSPERMS);
}
#endif

static int set_rsync_acl(const char *fname, acl_duo *duo_item,
			 SMB_ACL_TYPE_T type, stat_x *sxp, mode_t mode)
{
	if (type == SMB_ACL_TYPE_DEFAULT
	 && duo_item->racl.user_obj == NO_ENTRY) {
		int rc;
#ifdef SUPPORT_XATTRS
		/* --fake-super support: delete default ACL from xattrs. */
		if (am_root < 0)
			rc = del_def_xattr_acl(fname);
		else
#endif
			rc = sys_acl_delete_def_file(fname);
		if (rc < 0) {
			rsyserr(FERROR_XFER, errno, "set_acl: sys_acl_delete_def_file(%s)",
				fname);
			return -1;
		}
#ifdef SUPPORT_XATTRS
	} else if (am_root < 0) {
		/* --fake-super support: store ACLs in an xattr. */
		int cnt = duo_item->racl.names.count;
		size_t len = 4*4 + cnt * (4+4);
		char *buf = new_array(char, len);
		int rc;

		SIVAL(buf, 0, duo_item->racl.user_obj);
		SIVAL(buf, 4, duo_item->racl.group_obj);
		SIVAL(buf, 8, duo_item->racl.mask_obj);
		SIVAL(buf, 12, duo_item->racl.other_obj);

		if (cnt) {
			char *bp = buf + 4*4;
			id_access *ida = duo_item->racl.names.idas;
			for ( ; cnt--; ida++, bp += 4+4) {
				SIVAL(bp, 0, ida->id);
				SIVAL(bp, 4, ida->access);
			}
		}
		rc = set_xattr_acl(fname, type == SMB_ACL_TYPE_ACCESS, buf, len);
		free(buf);
		return rc;
#endif
	} else {
		mode_t cur_mode = sxp->st.st_mode;
		if (!duo_item->sacl
		 && !pack_smb_acl(&duo_item->sacl, &duo_item->racl))
			return -1;
#ifdef HAVE_OSX_ACLS
		(void)mode; /* eliminate compiler warning */
#else
		if (type == SMB_ACL_TYPE_ACCESS) {
			cur_mode = change_sacl_perms(duo_item->sacl, &duo_item->racl, cur_mode, mode);
			if (cur_mode == (mode_t)-1)
				return 0;
		}
#endif
		if (sys_acl_set_file(fname, type, duo_item->sacl) < 0) {
			rsyserr(FERROR_XFER, errno, "set_acl: sys_acl_set_file(%s, %s)",
				fname, str_acl_type(type));
			return -1;
		}
		if (type == SMB_ACL_TYPE_ACCESS)
			sxp->st.st_mode = cur_mode;
	}

	return 0;
}

/* Given a fname, this sets extended access ACL entries, the default ACL (for a
 * dir), and the regular mode bits on the file.  Call this with fname set to
 * NULL to just check if the ACL is different.
 *
 * If the ACL operation has a side-effect of changing the file's mode, the
 * sxp->st.st_mode value will be changed to match.
 *
 * Returns 0 for an unchanged ACL, 1 for changed, -1 for failed. */
int set_acl(const char *fname, const struct file_struct *file, stat_x *sxp, mode_t new_mode)
{
	int changed = 0;
	int32 ndx;
	BOOL eq;

	if (!dry_run && (read_only || list_only)) {
		errno = EROFS;
		return -1;
	}

	ndx = F_ACL(file);
	if (ndx >= 0 && (size_t)ndx < access_acl_list.count) {
		acl_duo *duo_item = access_acl_list.items;
		duo_item += ndx;
		eq = sxp->acc_acl
		  && rsync_acl_equal_enough(sxp->acc_acl, &duo_item->racl, new_mode);
		if (!eq) {
			changed = 1;
			if (!dry_run && fname
			 && set_rsync_acl(fname, duo_item, SMB_ACL_TYPE_ACCESS,
					  sxp, new_mode) < 0)
				return -1;
		}
	}

	if (!S_ISDIR(new_mode))
		return changed;

	ndx = F_DIR_DEFACL(file);
	if (ndx >= 0 && (size_t)ndx < default_acl_list.count) {
		acl_duo *duo_item = default_acl_list.items;
		duo_item += ndx;
		eq = sxp->def_acl && rsync_acl_equal(sxp->def_acl, &duo_item->racl);
		if (!eq) {
			changed = 1;
			if (!dry_run && fname
			 && set_rsync_acl(fname, duo_item, SMB_ACL_TYPE_DEFAULT,
					  sxp, new_mode) < 0)
				return -1;
		}
	}

	return changed;
}

/* Non-incremental recursion needs to convert all the received IDs.
 * This is done in a single pass after receiving the whole file-list. */
static void match_racl_ids(const item_list *racl_list)
{
	int list_cnt, name_cnt;
	acl_duo *duo_item = racl_list->items;
	for (list_cnt = racl_list->count; list_cnt--; duo_item++) {
		ida_entries *idal = &duo_item->racl.names;
		id_access *ida = idal->idas;
		for (name_cnt = idal->count; name_cnt--; ida++) {
			if (ida->access & NAME_IS_USER)
				ida->id = match_uid(ida->id);
			else
				ida->id = match_gid(ida->id, NULL);
		}
	}
}

void match_acl_ids(void)
{
	match_racl_ids(&access_acl_list);
	match_racl_ids(&default_acl_list);
}

/* This is used by dest_mode(). */
int default_perms_for_dir(const char *dir)
{
	rsync_acl racl;
	SMB_ACL_T sacl;
	BOOL ok;
	int perms;

	if (dir == NULL)
		dir = ".";
	perms = ACCESSPERMS & ~orig_umask;
	/* Read the directory's default ACL.  If it has none, this will successfully return an empty ACL. */
	sacl = sys_acl_get_file(dir, SMB_ACL_TYPE_DEFAULT);
	if (sacl == NULL) {
		/* Couldn't get an ACL.  Darn. */
		switch (errno) {
		case EINVAL:
			/* If SMB_ACL_TYPE_DEFAULT isn't valid, then the ACLs must be non-POSIX. */
			break;
#ifdef ENOTSUP
		case ENOTSUP:
#endif
		case ENOSYS:
			/* No ACLs are available. */
			break;
		default:
			if (dry_run && errno == ENOENT) {
				/* We're doing a dry run, so the containing directory
				 * wasn't actually created.  Don't worry about it. */
				break;
			}
			rprintf(FWARNING,
				"default_perms_for_dir: sys_acl_get_file(%s, %s): %s, falling back on umask\n",
				dir, str_acl_type(SMB_ACL_TYPE_DEFAULT), strerror(errno));
		}
		return perms;
	}

	/* Convert it. */
	racl = empty_rsync_acl;
	ok = unpack_smb_acl(sacl, &racl);
	sys_acl_free_acl(sacl);
	if (!ok) {
		rprintf(FWARNING, "default_perms_for_dir: unpack_smb_acl failed, falling back on umask\n");
		return perms;
	}

	/* Apply the permission-bit entries of the default ACL, if any. */
	if (racl.user_obj != NO_ENTRY) {
		perms = rsync_acl_get_perms(&racl);
		if (DEBUG_GTE(ACL, 1))
			rprintf(FINFO, "got ACL-based default perms %o for directory %s\n", perms, dir);
	}

	rsync_acl_free(&racl);
	return perms;
}

#endif /* SUPPORT_ACLS */
/*
 * Support rsync daemon authentication.
 *
 * Copyright (C) 1998-2000 Andrew Tridgell
 * Copyright (C) 2002-2022 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"
#include "itypes.h"
#include "ifuncs.h"

extern int read_only;
extern char *password_file;
extern struct name_num_obj valid_auth_checksums;

/***************************************************************************
encode a buffer using base64 - simple and slow algorithm. null terminates
the result.
  ***************************************************************************/
void base64_encode(const char *buf, int len, char *out, int pad)
{
	char *b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	int bit_offset, byte_offset, idx, i;
	const uchar *d = (const uchar *)buf;
	int bytes = (len*8 + 5)/6;

	for (i = 0; i < bytes; i++) {
		byte_offset = (i*6)/8;
		bit_offset = (i*6)%8;
		if (bit_offset < 3) {
			idx = (d[byte_offset] >> (2-bit_offset)) & 0x3F;
		} else {
			idx = (d[byte_offset] << (bit_offset-2)) & 0x3F;
			if (byte_offset+1 < len) {
				idx |= (d[byte_offset+1] >> (8-(bit_offset-2)));
			}
		}
		out[i] = b64[idx];
	}

	while (pad && (i % 4))
		out[i++] = '=';

	out[i] = '\0';
}

/* Generate a challenge buffer and return it base64-encoded. */
static void gen_challenge(const char *addr, char *challenge)
{
	char input[32];
	char digest[MAX_DIGEST_LEN];
	struct timeval tv;
	int len;

	memset(input, 0, sizeof input);

	strlcpy(input, addr, 17);
	sys_gettimeofday(&tv);
	SIVAL(input, 16, tv.tv_sec);
	SIVAL(input, 20, tv.tv_usec);
	SIVAL(input, 24, getpid());

	len = sum_init(valid_auth_checksums.negotiated_nni, 0);
	sum_update(input, sizeof input);
	sum_end(digest);

	base64_encode(digest, len, challenge, 0);
}

/* Generate an MD4 hash created from the combination of the password
 * and the challenge string and return it base64-encoded. */
static void generate_hash(const char *in, const char *challenge, char *out)
{
	char buf[MAX_DIGEST_LEN];
	int len;

	len = sum_init(valid_auth_checksums.negotiated_nni, 0);
	sum_update(in, strlen(in));
	sum_update(challenge, strlen(challenge));
	sum_end(buf);

	base64_encode(buf, len, out, 0);
}

/* Return the secret for a user from the secret file, null terminated.
 * Maximum length is len (not counting the null). */
static const char *check_secret(int module, const char *user, const char *group,
				const char *challenge, const char *pass)
{
	char line[1024];
	char pass2[MAX_DIGEST_LEN*2];
	const char *fname = lp_secrets_file(module);
	STRUCT_STAT st;
	int ok = 1;
	int user_len = strlen(user);
	int group_len = group ? strlen(group) : 0;
	char *err;
	FILE *fh;

	if (!fname || !*fname || (fh = fopen(fname, "r")) == NULL)
		return "no secrets file";

	if (do_fstat(fileno(fh), &st) == -1) {
		rsyserr(FLOG, errno, "fstat(%s)", fname);
		ok = 0;
	} else if (lp_strict_modes(module)) {
		if ((st.st_mode & 06) != 0) {
			rprintf(FLOG, "secrets file must not be other-accessible (see strict modes option)\n");
			ok = 0;
		} else if (MY_UID() == ROOT_UID && st.st_uid != ROOT_UID) {
			rprintf(FLOG, "secrets file must be owned by root when running as root (see strict modes)\n");
			ok = 0;
		}
	}
	if (!ok) {
		fclose(fh);
		return "ignoring secrets file";
	}

	if (*user == '#') {
		/* Reject attempt to match a comment. */
		fclose(fh);
		return "invalid username";
	}

	/* Try to find a line that starts with the user (or @group) name and a ':'. */
	err = "secret not found";
	while ((user || group) && fgets(line, sizeof line, fh) != NULL) {
		const char **ptr, *s = strtok(line, "\n\r");
		int len;
		if (!s)
			continue;
		if (*s == '@') {
			ptr = &group;
			len = group_len;
			s++;
		} else {
			ptr = &user;
			len = user_len;
		}
		if (!*ptr || strncmp(s, *ptr, len) != 0 || s[len] != ':')
			continue;
		generate_hash(s+len+1, challenge, pass2);
		if (strcmp(pass, pass2) == 0) {
			err = NULL;
			break;
		}
		err = "password mismatch";
		*ptr = NULL; /* Don't look for name again. */
	}

	fclose(fh);

	force_memzero(line, sizeof line);
	force_memzero(pass2, sizeof pass2);

	return err;
}

static const char *getpassf(const char *filename)
{
	STRUCT_STAT st;
	char buffer[512], *p;
	int n;

	if (!filename)
		return NULL;

	if (strcmp(filename, "-") == 0) {
		n = fgets(buffer, sizeof buffer, stdin) == NULL ? -1 : (int)strlen(buffer);
	} else {
		int fd;

		if ((fd = open(filename,O_RDONLY)) < 0) {
			rsyserr(FERROR, errno, "could not open password file %s", filename);
			exit_cleanup(RERR_SYNTAX);
		}

		if (do_stat(filename, &st) == -1) {
			rsyserr(FERROR, errno, "stat(%s)", filename);
			exit_cleanup(RERR_SYNTAX);
		}
		if ((st.st_mode & 06) != 0) {
			rprintf(FERROR, "ERROR: password file must not be other-accessible\n");
			exit_cleanup(RERR_SYNTAX);
		}
		if (MY_UID() == ROOT_UID && st.st_uid != ROOT_UID) {
			rprintf(FERROR, "ERROR: password file must be owned by root when running as root\n");
			exit_cleanup(RERR_SYNTAX);
		}

		n = read(fd, buffer, sizeof buffer - 1);
		close(fd);
	}

	if (n > 0) {
		buffer[n] = '\0';
		if ((p = strtok(buffer, "\n\r")) != NULL)
			return strdup(p);
	}

	rprintf(FERROR, "ERROR: failed to read a password from %s\n", filename);
	exit_cleanup(RERR_SYNTAX);
}

/* Possibly negotiate authentication with the client.  Use "leader" to
 * start off the auth if necessary.
 *
 * Return NULL if authentication failed.  Return "" if anonymous access.
 * Otherwise return username.
 */
char *auth_server(int f_in, int f_out, int module, const char *host,
		  const char *addr, const char *leader)
{
	char *users = lp_auth_users(module);
	char challenge[MAX_DIGEST_LEN*2];
	char line[BIGPATHBUFLEN];
	const char **auth_uid_groups = NULL;
	int auth_uid_groups_cnt = -1;
	const char *err = NULL;
	int group_match = -1;
	char *tok, *pass;
	char opt_ch = '\0';

	/* if no auth list then allow anyone in! */
	if (!users || !*users)
		return "";

	negotiate_daemon_auth(f_out, 0);
	gen_challenge(addr, challenge);

	io_printf(f_out, "%s%s\n", leader, challenge);

	if (!read_line_old(f_in, line, sizeof line, 0)
	 || (pass = strchr(line, ' ')) == NULL) {
		rprintf(FLOG, "auth failed on module %s from %s (%s): "
			"invalid challenge response\n",
			lp_name(module), host, addr);
		return NULL;
	}
	*pass++ = '\0';

	users = strdup(users);

	for (tok = strtok(users, " ,\t"); tok; tok = strtok(NULL, " ,\t")) {
		char *opts;
		/* See if the user appended :deny, :ro, or :rw. */
		if ((opts = strchr(tok, ':')) != NULL) {
			*opts++ = '\0';
			opt_ch = isUpper(opts) ? toLower(opts) : *opts;
			if (opt_ch == 'r') { /* handle ro and rw */
				opt_ch = isUpper(opts+1) ? toLower(opts+1) : opts[1];
				if (opt_ch == 'o')
					opt_ch = 'r';
				else if (opt_ch != 'w')
					opt_ch = '\0';
			} else if (opt_ch != 'd') /* if it's not deny, ignore it */
				opt_ch = '\0';
		} else
			opt_ch = '\0';
		if (*tok != '@') {
			/* Match the username */
			if (wildmatch(tok, line))
				break;
		} else {
#ifdef HAVE_GETGROUPLIST
			int j;
			/* See if authorizing user is a real user, and if so, see
			 * if it is in a group that matches tok+1 wildmat. */
			if (auth_uid_groups_cnt < 0) {
				item_list gid_list = EMPTY_ITEM_LIST;
				uid_t auth_uid;
				if (!user_to_uid(line, &auth_uid, False)
				 || getallgroups(auth_uid, &gid_list) != NULL)
					auth_uid_groups_cnt = 0;
				else {
					gid_t *gid_array = gid_list.items;
					auth_uid_groups_cnt = gid_list.count;
					auth_uid_groups = new_array(const char *, auth_uid_groups_cnt);
					for (j = 0; j < auth_uid_groups_cnt; j++)
						auth_uid_groups[j] = gid_to_group(gid_array[j]);
				}
			}
			for (j = 0; j < auth_uid_groups_cnt; j++) {
				if (auth_uid_groups[j] && wildmatch(tok+1, auth_uid_groups[j])) {
					group_match = j;
					break;
				}
			}
			if (group_match >= 0)
				break;
#else
			rprintf(FLOG, "your computer doesn't support getgrouplist(), so no @group authorization is possible.\n");
#endif
		}
	}

	free(users);

	if (!tok)
		err = "no matching rule";
	else if (opt_ch == 'd')
		err = "denied by rule";
	else {
		const char *group = group_match >= 0 ? auth_uid_groups[group_match] : NULL;
		err = check_secret(module, line, group, challenge, pass);
	}

	force_memzero(challenge, sizeof challenge);
	force_memzero(pass, strlen(pass));

	if (auth_uid_groups) {
		int j;
		for (j = 0; j < auth_uid_groups_cnt; j++) {
			if (auth_uid_groups[j])
				free((char*)auth_uid_groups[j]);
		}
		free(auth_uid_groups);
	}

	if (err) {
		rprintf(FLOG, "auth failed on module %s from %s (%s) for %s: %s\n",
			lp_name(module), host, addr, line, err);
		return NULL;
	}

	if (opt_ch == 'r')
		read_only = 1;
	else if (opt_ch == 'w')
		read_only = 0;

	return strdup(line);
}

void auth_client(int fd, const char *user, const char *challenge)
{
	const char *pass;
	char pass2[MAX_DIGEST_LEN*2];

	if (!user || !*user)
		user = "nobody";
	negotiate_daemon_auth(-1, 1);

	if (!(pass = getpassf(password_file))
	 && !(pass = getenv("RSYNC_PASSWORD"))) {
		/* XXX: cyeoh says that getpass is deprecated, because
		 * it may return a truncated password on some systems,
		 * and it is not in the LSB.
		 *
		 * Andrew Klein says that getpassphrase() is present
		 * on Solaris and reads up to 256 characters.
		 *
		 * OpenBSD has a readpassphrase() that might be more suitable.
		 */
		pass = getpass("Password: ");
	}

	if (!pass)
		pass = "";

	generate_hash(pass, challenge, pass2);
	io_printf(fd, "%s %s\n", user, pass2);
}
/*
 * Backup handling code.
 *
 * Copyright (C) 1999 Andrew Tridgell
 * Copyright (C) 2003-2022 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"
#include "ifuncs.h"

extern int am_root;
extern int preserve_acls;
extern int preserve_xattrs;
extern int preserve_devices;
extern int preserve_specials;
extern int preserve_links;
extern int safe_symlinks;
extern int backup_dir_len;
extern unsigned int backup_dir_remainder;
extern char backup_dir_buf[MAXPATHLEN];
extern char *backup_suffix;
extern char *backup_dir;

/* Returns -1 on error, 0 on missing dir, and 1 on present dir. */
static int validate_backup_dir(void)
{
	STRUCT_STAT st;

	if (do_lstat(backup_dir_buf, &st) < 0) {
		if (errno == ENOENT)
			return 0;
		rsyserr(FERROR, errno, "backup lstat %s failed", backup_dir_buf);
		return -1;
	}
	if (!S_ISDIR(st.st_mode)) {
		int flags = get_del_for_flag(st.st_mode) | DEL_FOR_BACKUP | DEL_RECURSE;
		if (delete_item(backup_dir_buf, st.st_mode, flags) == 0)
			return 0;
		return -1;
	}
	return 1;
}

/* Create a backup path from the given fname, putting the result into
 * backup_dir_buf.  Any new directories (compared to the prior backup
 * path) are ensured to exist as directories, replacing anything else
 * that may be in the way (e.g. a symlink). */
static BOOL copy_valid_path(const char *fname)
{
	const char *f;
	int val;
	BOOL ret = True;
	stat_x sx;
	char *b, *rel = backup_dir_buf + backup_dir_len, *name = rel;

	for (f = fname, b = rel; *f && *f == *b; f++, b++) {
		if (*b == '/')
			name = b + 1;
	}

	if (stringjoin(rel, backup_dir_remainder, fname, backup_suffix, NULL) >= backup_dir_remainder) {
		rprintf(FERROR, "backup filename too long\n");
		*name = '\0';
		return False;
	}

	for ( ; ; name = b + 1) {
		if ((b = strchr(name, '/')) == NULL)
			return True;
		*b = '\0';

		val = validate_backup_dir();
		if (val == 0)
			break;
		if (val < 0) {
			*name = '\0';
			return False;
		}

		*b = '/';
	}

	init_stat_x(&sx);

	for ( ; b; name = b + 1, b = strchr(name, '/')) {
		*b = '\0';

		while (do_mkdir(backup_dir_buf, ACCESSPERMS) < 0) {
			if (errno == EEXIST) {
				val = validate_backup_dir();
				if (val > 0)
					break;
				if (val == 0)
					continue;
			} else
				rsyserr(FERROR, errno, "backup mkdir %s failed", backup_dir_buf);
			*name = '\0';
			ret = False;
			goto cleanup;
		}

		/* Try to transfer the directory settings of the actual dir
		 * that the files are coming from. */
		if (x_stat(rel, &sx.st, NULL) < 0)
			rsyserr(FERROR, errno, "backup stat %s failed", full_fname(rel));
		else {
			struct file_struct *file;
			if (!(file = make_file(rel, NULL, NULL, 0, NO_FILTERS)))
				continue;
#ifdef SUPPORT_ACLS
			if (preserve_acls && !S_ISLNK(file->mode)) {
				get_acl(rel, &sx);
				cache_tmp_acl(file, &sx);
				free_acl(&sx);
			}
#endif
#ifdef SUPPORT_XATTRS
			if (preserve_xattrs) {
				get_xattr(rel, &sx);
				cache_tmp_xattr(file, &sx);
				free_xattr(&sx);
			}
#endif
			set_file_attrs(backup_dir_buf, file, NULL, NULL, 0);
			unmake_file(file);
		}

		*b = '/';
	}

  cleanup:

#ifdef SUPPORT_ACLS
	uncache_tmp_acls();
#endif
#ifdef SUPPORT_XATTRS
	uncache_tmp_xattrs();
#endif

	return ret;
}

/* Make a complete pathname for backup file and verify any new path elements. */
char *get_backup_name(const char *fname)
{
	if (backup_dir) {
		static int initialized = 0;
		if (!initialized) {
			int ret;
			if (backup_dir_len > 1)
				backup_dir_buf[backup_dir_len-1] = '\0';
			ret = make_path(backup_dir_buf, 0);
			if (backup_dir_len > 1)
				backup_dir_buf[backup_dir_len-1] = '/';
			if (ret < 0)
				return NULL;
			initialized = 1;
		}
		/* copy fname into backup_dir_buf while validating the dirs. */
		if (copy_valid_path(fname))
			return backup_dir_buf;
		/* copy_valid_path() has printed an error message. */
		return NULL;
	}

	if (stringjoin(backup_dir_buf, MAXPATHLEN, fname, backup_suffix, NULL) < MAXPATHLEN)
		return backup_dir_buf;

	rprintf(FERROR, "backup filename too long\n");
	return NULL;
}

/* Has same return codes as make_backup(). */
static inline int link_or_rename(const char *from, const char *to,
				 BOOL prefer_rename, STRUCT_STAT *stp)
{
#ifdef SUPPORT_HARD_LINKS
	if (!prefer_rename) {
#ifndef CAN_HARDLINK_SYMLINK
		if (S_ISLNK(stp->st_mode))
			return 0; /* Use copy code. */
#endif
#ifndef CAN_HARDLINK_SPECIAL
		if (IS_SPECIAL(stp->st_mode) || IS_DEVICE(stp->st_mode))
			return 0; /* Use copy code. */
#endif
		if (do_link(from, to) == 0) {
			if (DEBUG_GTE(BACKUP, 1))
				rprintf(FINFO, "make_backup: HLINK %s successful.\n", from);
			return 2;
		}
		/* We prefer to rename a regular file rather than copy it. */
		if (!S_ISREG(stp->st_mode) || errno == EEXIST || errno == EISDIR)
			return 0;
	}
#endif
	if (do_rename(from, to) == 0) {
		if (stp->st_nlink > 1 && !S_ISDIR(stp->st_mode)) {
			/* If someone has hard-linked the file into the backup
			 * dir, rename() might return success but do nothing! */
			robust_unlink(from); /* Just in case... */
		}
		if (DEBUG_GTE(BACKUP, 1))
			rprintf(FINFO, "make_backup: RENAME %s successful.\n", from);
		return 1;
	}
	return 0;
}

/* Hard-link, rename, or copy an item to the backup name.  Returns 0 for
 * failure, 1 if item was moved, 2 if item was duplicated or hard linked
 * into backup area, or 3 if item doesn't exist or isn't a regular file. */
int make_backup(const char *fname, BOOL prefer_rename)
{
	stat_x sx;
	struct file_struct *file;
	int save_preserve_xattrs;
	char *buf;
	int ret = 0;

	init_stat_x(&sx);
	/* Return success if no file to keep. */
	if (x_lstat(fname, &sx.st, NULL) < 0)
		return 3;

	if (!(buf = get_backup_name(fname)))
		return 0;

	/* Try a hard-link or a rename first.  Using rename is not atomic, but
	 * is more efficient than forcing a copy for larger files when no hard-
	 * linking is possible. */
	if ((ret = link_or_rename(fname, buf, prefer_rename, &sx.st)) != 0)
		goto success;
	if (errno == EEXIST || errno == EISDIR) {
		STRUCT_STAT bakst;
		if (do_lstat(buf, &bakst) == 0) {
			int flags = get_del_for_flag(bakst.st_mode) | DEL_FOR_BACKUP | DEL_RECURSE;
			if (delete_item(buf, bakst.st_mode, flags) != 0)
				return 0;
		}
		if ((ret = link_or_rename(fname, buf, prefer_rename, &sx.st)) != 0)
			goto success;
	}

	/* Fall back to making a copy. */
	if (!(file = make_file(fname, NULL, &sx.st, 0, NO_FILTERS)))
		return 3; /* the file could have disappeared */

#ifdef SUPPORT_ACLS
	if (preserve_acls && !S_ISLNK(file->mode)) {
		get_acl(fname, &sx);
		cache_tmp_acl(file, &sx);
		free_acl(&sx);
	}
#endif
#ifdef SUPPORT_XATTRS
	if (preserve_xattrs) {
		get_xattr(fname, &sx);
		cache_tmp_xattr(file, &sx);
		free_xattr(&sx);
	}
#endif

	/* Check to see if this is a device file, or link */
	if ((am_root && preserve_devices && IS_DEVICE(file->mode))
	 || (preserve_specials && IS_SPECIAL(file->mode))) {
		if (do_mknod(buf, file->mode, sx.st.st_rdev) < 0)
			rsyserr(FERROR, errno, "mknod %s failed", full_fname(buf));
		else if (DEBUG_GTE(BACKUP, 1))
			rprintf(FINFO, "make_backup: DEVICE %s successful.\n", fname);
		ret = 2;
	}

#ifdef SUPPORT_LINKS
	if (!ret && preserve_links && S_ISLNK(file->mode)) {
		const char *sl = F_SYMLINK(file);
		if (safe_symlinks && unsafe_symlink(sl, fname)) {
			if (INFO_GTE(SYMSAFE, 1)) {
				rprintf(FINFO, "not backing up unsafe symlink \"%s\" -> \"%s\"\n",
					fname, sl);
			}
			ret = 2;
		} else {
			if (do_symlink(sl, buf) < 0)
				rsyserr(FERROR, errno, "link %s -> \"%s\"", full_fname(buf), sl);
			else if (DEBUG_GTE(BACKUP, 1))
				rprintf(FINFO, "make_backup: SYMLINK %s successful.\n", fname);
			ret = 2;
		}
	}
#endif

	if (!ret && !S_ISREG(file->mode)) {
		if (INFO_GTE(NONREG, 1))
			rprintf(FINFO, "make_bak: skipping non-regular file %s\n", fname);
		unmake_file(file);
#ifdef SUPPORT_ACLS
		uncache_tmp_acls();
#endif
#ifdef SUPPORT_XATTRS
		uncache_tmp_xattrs();
#endif
		return 3;
	}

	/* Copy to backup tree if a file. */
	if (!ret) {
		if (copy_file(fname, buf, -1, file->mode) < 0) {
			rsyserr(FERROR, errno, "keep_backup failed: %s -> \"%s\"",
				full_fname(fname), buf);
			unmake_file(file);
#ifdef SUPPORT_ACLS
			uncache_tmp_acls();
#endif
#ifdef SUPPORT_XATTRS
			uncache_tmp_xattrs();
#endif
			return 0;
		}
		if (DEBUG_GTE(BACKUP, 1))
			rprintf(FINFO, "make_backup: COPY %s successful.\n", fname);
		ret = 2;
	}

	save_preserve_xattrs = preserve_xattrs;
	preserve_xattrs = 0;
	set_file_attrs(buf, file, NULL, fname, ATTRS_ACCURATE_TIME);
	preserve_xattrs = save_preserve_xattrs;

	unmake_file(file);
#ifdef SUPPORT_ACLS
	uncache_tmp_acls();
#endif
#ifdef SUPPORT_XATTRS
	uncache_tmp_xattrs();
#endif

  success:
	if (INFO_GTE(BACKUP, 1))
		rprintf(FINFO, "backed up %s to %s\n", fname, buf);
	return ret;
}
/*
 * Support for the batch-file options.
 *
 * Copyright (C) 1999 Weiss
 * Copyright (C) 2004 Chris Shoemaker
 * Copyright (C) 2004-2022 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"
#include <zlib.h>
#include <time.h>

extern int eol_nulls;
extern int recurse;
extern int xfer_dirs;
extern int preserve_links;
extern int preserve_hard_links;
extern int preserve_devices;
extern int preserve_uid;
extern int preserve_gid;
extern int preserve_acls;
extern int preserve_xattrs;
extern int always_checksum;
extern int do_compression;
extern int inplace;
extern int append_mode;
extern int write_batch;
extern int protocol_version;
extern int raw_argc, cooked_argc;
extern char **raw_argv, **cooked_argv;
extern char *batch_name;
#ifdef ICONV_OPTION
extern char *iconv_opt;
#endif

extern filter_rule_list filter_list;

int batch_fd = -1;
int batch_sh_fd = -1;
int batch_stream_flags;

static int tweaked_append;
static int tweaked_append_verify;
static int tweaked_iconv;

static int *flag_ptr[] = {
	&recurse,		/* 0 */
	&preserve_uid,		/* 1 */
	&preserve_gid,		/* 2 */
	&preserve_links,	/* 3 */
	&preserve_devices,	/* 4 */
	&preserve_hard_links,	/* 5 */
	&always_checksum,	/* 6 */
	&xfer_dirs,		/* 7 (protocol 29) */
	&do_compression,	/* 8 (protocol 29) */
	&tweaked_iconv,		/* 9  (protocol 30) */
	&preserve_acls,		/* 10 (protocol 30) */
	&preserve_xattrs,	/* 11 (protocol 30) */
	&inplace,		/* 12 (protocol 30) */
	&tweaked_append,	/* 13 (protocol 30) */
	&tweaked_append_verify,	/* 14 (protocol 30) */
	NULL
};

static char *flag_name[] = {
	"--recurse (-r)",
	"--owner (-o)",
	"--group (-g)",
	"--links (-l)",
	"--devices (-D)",
	"--hard-links (-H)",
	"--checksum (-c)",
	"--dirs (-d)",
	"--compress (-z)",
	"--iconv",
	"--acls (-A)",
	"--xattrs (-X)",
	"--inplace",
	"--append",
	"--append-verify",
	NULL
};

void write_stream_flags(int fd)
{
	int i, flags;

	tweaked_append = append_mode == 1;
	tweaked_append_verify = append_mode == 2;
#ifdef ICONV_OPTION
	tweaked_iconv = iconv_opt != NULL;
#endif

	/* Start the batch file with a bitmap of data-stream-affecting
	 * flags. */
	for (i = 0, flags = 0; flag_ptr[i]; i++) {
		if (*flag_ptr[i])
			flags |= 1 << i;
	}
	write_int(fd, flags);
}

void read_stream_flags(int fd)
{
	batch_stream_flags = read_int(fd);
}

void check_batch_flags(void)
{
	int i;

	if (protocol_version < 29)
		flag_ptr[7] = NULL;
	else if (protocol_version < 30)
		flag_ptr[9] = NULL;
	tweaked_append = append_mode == 1;
	tweaked_append_verify = append_mode == 2;
#ifdef ICONV_OPTION
	tweaked_iconv = iconv_opt != NULL;
#endif
	for (i = 0; flag_ptr[i]; i++) {
		int set = batch_stream_flags & (1 << i) ? 1 : 0;
		if (*flag_ptr[i] != set) {
			if (i == 9) {
				rprintf(FERROR,
					"%s specify the --iconv option to use this batch file.\n",
					set ? "Please" : "Do not");
				exit_cleanup(RERR_SYNTAX);
			}
			if (INFO_GTE(MISC, 1)) {
				rprintf(FINFO,
					"%sing the %s option to match the batchfile.\n",
					set ? "Sett" : "Clear", flag_name[i]);
			}
			*flag_ptr[i] = set;
		}
	}
	if (protocol_version < 29) {
		if (recurse)
			xfer_dirs |= 1;
		else if (xfer_dirs < 2)
			xfer_dirs = 0;
	}

	if (tweaked_append)
		append_mode = 1;
	else if (tweaked_append_verify)
		append_mode = 2;
}

static int write_arg(const char *arg)
{
	const char *x, *s;
	int len, err = 0;

	if (*arg == '-' && (x = strchr(arg, '=')) != NULL) {
		err |= write(batch_sh_fd, arg, x - arg + 1) != x - arg + 1;
		arg += x - arg + 1;
	}

	if (strpbrk(arg, " \"'&;|[]()$#!*?^\\") != NULL) {
		err |= write(batch_sh_fd, "'", 1) != 1;
		for (s = arg; (x = strchr(s, '\'')) != NULL; s = x + 1) {
			err |= write(batch_sh_fd, s, x - s + 1) != x - s + 1;
			err |= write(batch_sh_fd, "'", 1) != 1;
		}
		len = strlen(s);
		err |= write(batch_sh_fd, s, len) != len;
		err |= write(batch_sh_fd, "'", 1) != 1;
		return err;
	}

	len = strlen(arg);
	err |= write(batch_sh_fd, arg, len) != len;

	return err;
}

/* Writes out a space and then an option (or other string) with an optional "=" + arg suffix. */
static int write_opt(const char *opt, const char *arg)
{
	int len = strlen(opt);
	int err = write(batch_sh_fd, " ", 1) != 1;
	err = write(batch_sh_fd, opt, len) != len ? 1 : 0;
	if (arg) {
		err |= write(batch_sh_fd, "=", 1) != 1;
		err |= write_arg(arg);
	}
	return err;
}

static void write_filter_rules(int fd)
{
	filter_rule *ent;

	write_sbuf(fd, " <<'#E#'\n");
	for (ent = filter_list.head; ent; ent = ent->next) {
		unsigned int plen;
		char *p = get_rule_prefix(ent, "- ", 0, &plen);
		write_buf(fd, p, plen);
		write_sbuf(fd, ent->pattern);
		if (ent->rflags & FILTRULE_DIRECTORY)
			write_byte(fd, '/');
		write_byte(fd, eol_nulls ? 0 : '\n');
	}
	if (eol_nulls)
		write_sbuf(fd, ";\n");
	write_sbuf(fd, "#E#");
}

/* This sets batch_fd and (for --write-batch) batch_sh_fd. */
void open_batch_files(void)
{
	if (write_batch) {
		char filename[MAXPATHLEN];

		stringjoin(filename, sizeof filename, batch_name, ".sh", NULL);

		batch_sh_fd = do_open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IXUSR);
		if (batch_sh_fd < 0) {
			rsyserr(FERROR, errno, "Batch file %s open error", full_fname(filename));
			exit_cleanup(RERR_FILESELECT);
		}

		batch_fd = do_open(batch_name, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
	} else if (strcmp(batch_name, "-") == 0)
		batch_fd = STDIN_FILENO;
	else
		batch_fd = do_open(batch_name, O_RDONLY, S_IRUSR | S_IWUSR);

	if (batch_fd < 0) {
		rsyserr(FERROR, errno, "Batch file %s open error", full_fname(batch_name));
		exit_cleanup(RERR_FILEIO);
	}
}

/* This routine tries to write out an equivalent --read-batch command
 * given the user's --write-batch args.  However, it doesn't really
 * understand most of the options, so it uses some overly simple
 * heuristics to munge the command line into something that will
 * (hopefully) work. */
void write_batch_shell_file(void)
{
	int i, j, len, err = 0;
	char *p, *p2;

	/* Write argvs info to BATCH.sh file */
	err |= write_arg(raw_argv[0]);
	if (filter_list.head) {
		if (protocol_version >= 29)
			err |= write_opt("--filter", "._-");
		else
			err |= write_opt("--exclude-from", "-");
	}

	/* Elide the filename args from the option list, but scan for them in reverse. */
	for (i = raw_argc-1, j = cooked_argc-1; i > 0 && j >= 0; i--) {
		if (strcmp(raw_argv[i], cooked_argv[j]) == 0) {
			raw_argv[i] = NULL;
			j--;
		}
	}

	for (i = 1; i < raw_argc; i++) {
		if (!(p = raw_argv[i]))
			continue;
		if (strncmp(p, "--files-from", 12) == 0
		 || strncmp(p, "--filter", 8) == 0
		 || strncmp(p, "--include", 9) == 0
		 || strncmp(p, "--exclude", 9) == 0) {
			if (strchr(p, '=') == NULL)
				i++;
			continue;
		}
		if (strcmp(p, "-f") == 0) {
			i++;
			continue;
		}
		if (strncmp(p, "--write-batch", len = 13) == 0
		 || strncmp(p, "--only-write-batch", len = 18) == 0)
			err |= write_opt("--read-batch", p[len] == '=' ? p + len + 1 : NULL);
		else {
			err |= write(batch_sh_fd, " ", 1) != 1;
			err |= write_arg(p);
		}
	}
	if (!(p = check_for_hostspec(cooked_argv[cooked_argc - 1], &p2, &i)))
		p = cooked_argv[cooked_argc - 1];
	err |= write_opt("${1:-", NULL);
	err |= write_arg(p);
	err |= write(batch_sh_fd, "}", 1) != 1;
	if (filter_list.head)
		write_filter_rules(batch_sh_fd);
	if (write(batch_sh_fd, "\n", 1) != 1 || close(batch_sh_fd) < 0 || err) {
		rsyserr(FERROR, errno, "Batch file %s.sh write error", batch_name);
		exit_cleanup(RERR_FILEIO);
	}
	batch_sh_fd = -1;
}
/*
 * Routines to support checksumming of bytes.
 *
 * Copyright (C) 1996 Andrew Tridgell
 * Copyright (C) 1996 Paul Mackerras
 * Copyright (C) 2004-2023 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to dynamically link rsync with the OpenSSL and xxhash
 * libraries when those libraries are being distributed in compliance
 * with their license terms, and to distribute a dynamically linked
 * combination of rsync and these libraries.  This is also considered
 * to be covered under the GPL's System Libraries exception.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"

#ifdef SUPPORT_XXHASH
#include <xxhash.h>
# if XXH_VERSION_NUMBER >= 800
#  define SUPPORT_XXH3 1
# endif
#endif

extern int am_server;
extern int whole_file;
extern int checksum_seed;
extern int protocol_version;
extern int proper_seed_order;
extern const char *checksum_choice;

#define NNI_BUILTIN (1<<0)
#define NNI_EVP (1<<1)
#define NNI_EVP_OK (1<<2)

struct name_num_item valid_checksums_items[] = {
#ifdef SUPPORT_XXH3
	{ CSUM_XXH3_128, 0, "xxh128", NULL },
	{ CSUM_XXH3_64, 0, "xxh3", NULL },
#endif
#ifdef SUPPORT_XXHASH
	{ CSUM_XXH64, 0, "xxh64", NULL },
	{ CSUM_XXH64, 0, "xxhash", NULL },
#endif
	{ CSUM_MD5, NNI_BUILTIN|NNI_EVP, "md5", NULL },
	{ CSUM_MD4, NNI_BUILTIN|NNI_EVP, "md4", NULL },
#ifdef SHA_DIGEST_LENGTH
	{ CSUM_SHA1, NNI_EVP, "sha1", NULL },
#endif
	{ CSUM_NONE, 0, "none", NULL },
	{ 0, 0, NULL, NULL }
};

struct name_num_obj valid_checksums = {
	"checksum", NULL, 0, 0, valid_checksums_items
};

struct name_num_item valid_auth_checksums_items[] = {
#ifdef SHA512_DIGEST_LENGTH
	{ CSUM_SHA512, NNI_EVP, "sha512", NULL },
#endif
#ifdef SHA256_DIGEST_LENGTH
	{ CSUM_SHA256, NNI_EVP, "sha256", NULL },
#endif
#ifdef SHA_DIGEST_LENGTH
	{ CSUM_SHA1, NNI_EVP, "sha1", NULL },
#endif
	{ CSUM_MD5, NNI_BUILTIN|NNI_EVP, "md5", NULL },
	{ CSUM_MD4, NNI_BUILTIN|NNI_EVP, "md4", NULL },
	{ 0, 0, NULL, NULL }
};

struct name_num_obj valid_auth_checksums = {
	"daemon auth checksum", NULL, 0, 0, valid_auth_checksums_items
};

/* These cannot make use of openssl, so they're marked just as built-in */
struct name_num_item implied_checksum_md4 =
    { CSUM_MD4, NNI_BUILTIN, "md4", NULL };
struct name_num_item implied_checksum_md5 =
    { CSUM_MD5, NNI_BUILTIN, "md5", NULL };

struct name_num_item *xfer_sum_nni; /* used for the transfer checksum2 computations */
int xfer_sum_len;
struct name_num_item *file_sum_nni; /* used for the pre-transfer --checksum computations */
int file_sum_len, file_sum_extra_cnt;

#ifdef USE_OPENSSL
const EVP_MD *xfer_sum_evp_md;
const EVP_MD *file_sum_evp_md;
EVP_MD_CTX *ctx_evp = NULL;
#endif

static int initialized_choices = 0;

struct name_num_item *parse_csum_name(const char *name, int len)
{
	struct name_num_item *nni;

	if (len < 0 && name)
		len = strlen(name);

	init_checksum_choices();

	if (!name || (len == 4 && strncasecmp(name, "auto", 4) == 0)) {
		if (protocol_version >= 30) {
			if (!proper_seed_order)
				return &implied_checksum_md5;
			name = "md5";
			len = 3;
		} else {
			if (protocol_version >= 27)
				implied_checksum_md4.num = CSUM_MD4_OLD;
			else if (protocol_version >= 21)
				implied_checksum_md4.num = CSUM_MD4_BUSTED;
			else
				implied_checksum_md4.num = CSUM_MD4_ARCHAIC;
			return &implied_checksum_md4;
		}
	}

	nni = get_nni_by_name(&valid_checksums, name, len);

	if (!nni) {
		rprintf(FERROR, "unknown checksum name: %s\n", name);
		exit_cleanup(RERR_UNSUPPORTED);
	}

	return nni;
}

#ifdef USE_OPENSSL
static const EVP_MD *csum_evp_md(struct name_num_item *nni)
{
	const EVP_MD *emd;
	if (!(nni->flags & NNI_EVP))
		return NULL;

#ifdef USE_MD5_ASM
	if (nni->num == CSUM_MD5)
		emd = NULL;
	else
#endif
		emd = EVP_get_digestbyname(nni->name);
	if (emd && !(nni->flags & NNI_EVP_OK)) { /* Make sure it works before we advertise it */
		if (!ctx_evp && !(ctx_evp = EVP_MD_CTX_create()))
			out_of_memory("csum_evp_md");
		/* Some routines are marked as legacy and are not enabled in the openssl.cnf file.
		 * If we can't init the emd, we'll fall back to our built-in code. */
		if (EVP_DigestInit_ex(ctx_evp, emd, NULL) == 0)
			emd = NULL;
		else
			nni->flags = (nni->flags & ~NNI_BUILTIN) | NNI_EVP_OK;
	}
	if (!emd)
		nni->flags &= ~NNI_EVP;
	return emd;
}
#endif

void parse_checksum_choice(int final_call)
{
	if (valid_checksums.negotiated_nni)
		xfer_sum_nni = file_sum_nni = valid_checksums.negotiated_nni;
	else {
		char *cp = checksum_choice ? strchr(checksum_choice, ',') : NULL;
		if (cp) {
			xfer_sum_nni = parse_csum_name(checksum_choice, cp - checksum_choice);
			file_sum_nni = parse_csum_name(cp+1, -1);
		} else
			xfer_sum_nni = file_sum_nni = parse_csum_name(checksum_choice, -1);
		if (am_server && checksum_choice)
			validate_choice_vs_env(NSTR_CHECKSUM, xfer_sum_nni->num, file_sum_nni->num);
	}
	xfer_sum_len = csum_len_for_type(xfer_sum_nni->num, 0);
	file_sum_len = csum_len_for_type(file_sum_nni->num, 0);
#ifdef USE_OPENSSL
	xfer_sum_evp_md = csum_evp_md(xfer_sum_nni);
	file_sum_evp_md = csum_evp_md(file_sum_nni);
#endif

	file_sum_extra_cnt = (file_sum_len + EXTRA_LEN - 1) / EXTRA_LEN;

	if (xfer_sum_nni->num == CSUM_NONE)
		whole_file = 1;

	/* Snag the checksum name for both write_batch's option output & the following debug output. */
	if (valid_checksums.negotiated_nni)
		checksum_choice = valid_checksums.negotiated_nni->name;
	else if (checksum_choice == NULL)
		checksum_choice = xfer_sum_nni->name;

	if (final_call && DEBUG_GTE(NSTR, am_server ? 3 : 1)) {
		rprintf(FINFO, "%s%s checksum: %s\n",
			am_server ? "Server" : "Client",
			valid_checksums.negotiated_nni ? " negotiated" : "",
			checksum_choice);
	}
}

int csum_len_for_type(int cst, BOOL flist_csum)
{
	switch (cst) {
	  case CSUM_NONE:
		return 1;
	  case CSUM_MD4_ARCHAIC:
		/* The oldest checksum code is rather weird: the file-list code only sent
		 * 2-byte checksums, but all other checksums were full MD4 length. */
		return flist_csum ? 2 : MD4_DIGEST_LEN;
	  case CSUM_MD4:
	  case CSUM_MD4_OLD:
	  case CSUM_MD4_BUSTED:
		return MD4_DIGEST_LEN;
	  case CSUM_MD5:
		return MD5_DIGEST_LEN;
#ifdef SHA_DIGEST_LENGTH
	  case CSUM_SHA1:
		return SHA_DIGEST_LENGTH;
#endif
#ifdef SHA256_DIGEST_LENGTH
	  case CSUM_SHA256:
		return SHA256_DIGEST_LENGTH;
#endif
#ifdef SHA512_DIGEST_LENGTH
	  case CSUM_SHA512:
		return SHA512_DIGEST_LENGTH;
#endif
	  case CSUM_XXH64:
	  case CSUM_XXH3_64:
		return 64/8;
	  case CSUM_XXH3_128:
		return 128/8;
	  default: /* paranoia to prevent missing case values */
		exit_cleanup(RERR_UNSUPPORTED);
	}
	return 0;
}

/* Returns 0 if the checksum is not canonical (i.e. it includes a seed value).
 * Returns 1 if the public sum order matches our internal sum order.
 * Returns -1 if the public sum order is the reverse of our internal sum order.
 */
int canonical_checksum(int csum_type)
{
	switch (csum_type) {
	  case CSUM_NONE:
	  case CSUM_MD4_ARCHAIC:
	  case CSUM_MD4_OLD:
	  case CSUM_MD4_BUSTED:
		break;
	  case CSUM_MD4:
	  case CSUM_MD5:
	  case CSUM_SHA1:
	  case CSUM_SHA256:
	  case CSUM_SHA512:
		return -1;
	  case CSUM_XXH64:
	  case CSUM_XXH3_64:
	  case CSUM_XXH3_128:
		return 1;
	  default: /* paranoia to prevent missing case values */
		exit_cleanup(RERR_UNSUPPORTED);
	}
	return 0;
}

#ifndef USE_ROLL_SIMD /* See simd-checksum-*.cpp. */
/*
  a simple 32 bit checksum that can be updated from either end
  (inspired by Mark Adler's Adler-32 checksum)
  */
uint32 get_checksum1(char *buf1, int32 len)
{
	int32 i;
	uint32 s1, s2;
	schar *buf = (schar *)buf1;

	s1 = s2 = 0;
	for (i = 0; i < (len-4); i+=4) {
		s2 += 4*(s1 + buf[i]) + 3*buf[i+1] + 2*buf[i+2] + buf[i+3] + 10*CHAR_OFFSET;
		s1 += (buf[i+0] + buf[i+1] + buf[i+2] + buf[i+3] + 4*CHAR_OFFSET);
	}
	for (; i < len; i++) {
		s1 += (buf[i]+CHAR_OFFSET); s2 += s1;
	}
	return (s1 & 0xffff) + (s2 << 16);
}
#endif

/* The "sum" buffer must be at least MAX_DIGEST_LEN bytes! */
void get_checksum2(char *buf, int32 len, char *sum)
{
#ifdef USE_OPENSSL
	if (xfer_sum_evp_md) {
		static EVP_MD_CTX *evp = NULL;
		uchar seedbuf[4];
		if (!evp && !(evp = EVP_MD_CTX_create()))
			out_of_memory("get_checksum2");
		EVP_DigestInit_ex(evp, xfer_sum_evp_md, NULL);
		if (checksum_seed) {
			SIVALu(seedbuf, 0, checksum_seed);
			EVP_DigestUpdate(evp, seedbuf, 4);
		}
		EVP_DigestUpdate(evp, (uchar *)buf, len);
		EVP_DigestFinal_ex(evp, (uchar *)sum, NULL);
	} else
#endif
	switch (xfer_sum_nni->num) {
#ifdef SUPPORT_XXHASH
	  case CSUM_XXH64:
		SIVAL64(sum, 0, XXH64(buf, len, checksum_seed));
		break;
#endif
#ifdef SUPPORT_XXH3
	  case CSUM_XXH3_64:
		SIVAL64(sum, 0, XXH3_64bits_withSeed(buf, len, checksum_seed));
		break;
	  case CSUM_XXH3_128: {
		XXH128_hash_t digest = XXH3_128bits_withSeed(buf, len, checksum_seed);
		SIVAL64(sum, 0, digest.low64);
		SIVAL64(sum, 8, digest.high64);
		break;
	  }
#endif
	  case CSUM_MD5: {
		md_context m5;
		uchar seedbuf[4];
		md5_begin(&m5);
		if (proper_seed_order) {
			if (checksum_seed) {
				SIVALu(seedbuf, 0, checksum_seed);
				md5_update(&m5, seedbuf, 4);
			}
			md5_update(&m5, (uchar *)buf, len);
		} else {
			md5_update(&m5, (uchar *)buf, len);
			if (checksum_seed) {
				SIVALu(seedbuf, 0, checksum_seed);
				md5_update(&m5, seedbuf, 4);
			}
		}
		md5_result(&m5, (uchar *)sum);
		break;
	  }
	  case CSUM_MD4:
	  case CSUM_MD4_OLD:
	  case CSUM_MD4_BUSTED:
	  case CSUM_MD4_ARCHAIC: {
		md_context m;
		int32 i;
		static char *buf1;
		static int32 len1;

		mdfour_begin(&m);

		if (len > len1) {
			if (buf1)
				free(buf1);
			buf1 = new_array(char, len+4);
			len1 = len;
		}

		memcpy(buf1, buf, len);
		if (checksum_seed) {
			SIVAL(buf1,len,checksum_seed);
			len += 4;
		}

		for (i = 0; i + CSUM_CHUNK <= len; i += CSUM_CHUNK)
			mdfour_update(&m, (uchar *)(buf1+i), CSUM_CHUNK);

		/*
		 * Prior to version 27 an incorrect MD4 checksum was computed
		 * by failing to call mdfour_tail() for block sizes that
		 * are multiples of 64.  This is fixed by calling mdfour_update()
		 * even when there are no more bytes.
		 */
		if (len - i > 0 || xfer_sum_nni->num > CSUM_MD4_BUSTED)
			mdfour_update(&m, (uchar *)(buf1+i), len-i);

		mdfour_result(&m, (uchar *)sum);
		break;
	  }
	  default: /* paranoia to prevent missing case values */
		exit_cleanup(RERR_UNSUPPORTED);
	}
}

void file_checksum(const char *fname, const STRUCT_STAT *st_p, char *sum)
{
	struct map_struct *buf;
	OFF_T i, len = st_p->st_size;
	int32 remainder;
	int fd;

	fd = do_open_checklinks(fname);
	if (fd == -1) {
		memset(sum, 0, file_sum_len);
		return;
	}

	buf = map_file(fd, len, MAX_MAP_SIZE, CHUNK_SIZE);

#ifdef USE_OPENSSL
	if (file_sum_evp_md) {
		static EVP_MD_CTX *evp = NULL;
		if (!evp && !(evp = EVP_MD_CTX_create()))
			out_of_memory("file_checksum");

		EVP_DigestInit_ex(evp, file_sum_evp_md, NULL);

		for (i = 0; i + CHUNK_SIZE <= len; i += CHUNK_SIZE)
			EVP_DigestUpdate(evp, (uchar *)map_ptr(buf, i, CHUNK_SIZE), CHUNK_SIZE);

		remainder = (int32)(len - i);
		if (remainder > 0)
			EVP_DigestUpdate(evp, (uchar *)map_ptr(buf, i, remainder), remainder);

		EVP_DigestFinal_ex(evp, (uchar *)sum, NULL);
	} else
#endif
	switch (file_sum_nni->num) {
#ifdef SUPPORT_XXHASH
	  case CSUM_XXH64: {
		static XXH64_state_t* state = NULL;
		if (!state && !(state = XXH64_createState()))
			out_of_memory("file_checksum");

		XXH64_reset(state, 0);

		for (i = 0; i + CHUNK_SIZE <= len; i += CHUNK_SIZE)
			XXH64_update(state, (uchar *)map_ptr(buf, i, CHUNK_SIZE), CHUNK_SIZE);

		remainder = (int32)(len - i);
		if (remainder > 0)
			XXH64_update(state, (uchar *)map_ptr(buf, i, remainder), remainder);

		SIVAL64(sum, 0, XXH64_digest(state));
		break;
	  }
#endif
#ifdef SUPPORT_XXH3
	  case CSUM_XXH3_64: {
		static XXH3_state_t* state = NULL;
		if (!state && !(state = XXH3_createState()))
			out_of_memory("file_checksum");

		XXH3_64bits_reset(state);

		for (i = 0; i + CHUNK_SIZE <= len; i += CHUNK_SIZE)
			XXH3_64bits_update(state, (uchar *)map_ptr(buf, i, CHUNK_SIZE), CHUNK_SIZE);

		remainder = (int32)(len - i);
		if (remainder > 0)
			XXH3_64bits_update(state, (uchar *)map_ptr(buf, i, remainder), remainder);

		SIVAL64(sum, 0, XXH3_64bits_digest(state));
		break;
	  }
	  case CSUM_XXH3_128: {
		XXH128_hash_t digest;
		static XXH3_state_t* state = NULL;
		if (!state && !(state = XXH3_createState()))
			out_of_memory("file_checksum");

		XXH3_128bits_reset(state);

		for (i = 0; i + CHUNK_SIZE <= len; i += CHUNK_SIZE)
			XXH3_128bits_update(state, (uchar *)map_ptr(buf, i, CHUNK_SIZE), CHUNK_SIZE);

		remainder = (int32)(len - i);
		if (remainder > 0)
			XXH3_128bits_update(state, (uchar *)map_ptr(buf, i, remainder), remainder);

		digest = XXH3_128bits_digest(state);
		SIVAL64(sum, 0, digest.low64);
		SIVAL64(sum, 8, digest.high64);
		break;
	  }
#endif
	  case CSUM_MD5: {
		md_context m5;

		md5_begin(&m5);

		for (i = 0; i + CHUNK_SIZE <= len; i += CHUNK_SIZE)
			md5_update(&m5, (uchar *)map_ptr(buf, i, CHUNK_SIZE), CHUNK_SIZE);

		remainder = (int32)(len - i);
		if (remainder > 0)
			md5_update(&m5, (uchar *)map_ptr(buf, i, remainder), remainder);

		md5_result(&m5, (uchar *)sum);
		break;
	  }
	  case CSUM_MD4:
	  case CSUM_MD4_OLD:
	  case CSUM_MD4_BUSTED:
	  case CSUM_MD4_ARCHAIC: {
		md_context m;

		mdfour_begin(&m);

		for (i = 0; i + CSUM_CHUNK <= len; i += CSUM_CHUNK)
			mdfour_update(&m, (uchar *)map_ptr(buf, i, CSUM_CHUNK), CSUM_CHUNK);

		/* Prior to version 27 an incorrect MD4 checksum was computed
		 * by failing to call mdfour_tail() for block sizes that
		 * are multiples of 64.  This is fixed by calling mdfour_update()
		 * even when there are no more bytes. */
		remainder = (int32)(len - i);
		if (remainder > 0 || file_sum_nni->num > CSUM_MD4_BUSTED)
			mdfour_update(&m, (uchar *)map_ptr(buf, i, remainder), remainder);

		mdfour_result(&m, (uchar *)sum);
		break;
	  }
	  default:
		rprintf(FERROR, "Invalid checksum-choice for --checksum: %s (%d)\n",
			file_sum_nni->name, file_sum_nni->num);
		exit_cleanup(RERR_UNSUPPORTED);
	}

	close(fd);
	unmap_file(buf);
}

static int32 sumresidue;
static md_context ctx_md;
#ifdef SUPPORT_XXHASH
static XXH64_state_t* xxh64_state;
#endif
#ifdef SUPPORT_XXH3
static XXH3_state_t* xxh3_state;
#endif
static struct name_num_item *cur_sum_nni;
int cur_sum_len;

#ifdef USE_OPENSSL
static const EVP_MD *cur_sum_evp_md;
#endif

/* Initialize a hash digest accumulator.  Data is supplied via
 * sum_update() and the resulting binary digest is retrieved via
 * sum_end().  This only supports one active sum at a time. */
int sum_init(struct name_num_item *nni, int seed)
{
	char s[4];

	if (!nni)
		nni = parse_csum_name(NULL, 0);
	cur_sum_nni = nni;
	cur_sum_len = csum_len_for_type(nni->num, 0);
#ifdef USE_OPENSSL
	cur_sum_evp_md = csum_evp_md(nni);
#endif

#ifdef USE_OPENSSL
	if (cur_sum_evp_md) {
		if (!ctx_evp && !(ctx_evp = EVP_MD_CTX_create()))
			out_of_memory("file_checksum");
		EVP_DigestInit_ex(ctx_evp, cur_sum_evp_md, NULL);
	} else
#endif
	switch (cur_sum_nni->num) {
#ifdef SUPPORT_XXHASH
	  case CSUM_XXH64:
		if (!xxh64_state && !(xxh64_state = XXH64_createState()))
			out_of_memory("sum_init");
		XXH64_reset(xxh64_state, 0);
		break;
#endif
#ifdef SUPPORT_XXH3
	  case CSUM_XXH3_64:
		if (!xxh3_state && !(xxh3_state = XXH3_createState()))
			out_of_memory("sum_init");
		XXH3_64bits_reset(xxh3_state);
		break;
	  case CSUM_XXH3_128:
		if (!xxh3_state && !(xxh3_state = XXH3_createState()))
			out_of_memory("sum_init");
		XXH3_128bits_reset(xxh3_state);
		break;
#endif
	  case CSUM_MD5:
		md5_begin(&ctx_md);
		break;
	  case CSUM_MD4:
		mdfour_begin(&ctx_md);
		sumresidue = 0;
		break;
	  case CSUM_MD4_OLD:
	  case CSUM_MD4_BUSTED:
	  case CSUM_MD4_ARCHAIC:
		mdfour_begin(&ctx_md);
		sumresidue = 0;
		SIVAL(s, 0, seed);
		sum_update(s, 4);
		break;
	  case CSUM_NONE:
		break;
	  default: /* paranoia to prevent missing case values */
		exit_cleanup(RERR_UNSUPPORTED);
	}

	return cur_sum_len;
}

/* Feed data into a hash digest accumulator. */
void sum_update(const char *p, int32 len)
{
#ifdef USE_OPENSSL
	if (cur_sum_evp_md) {
		EVP_DigestUpdate(ctx_evp, (uchar *)p, len);
	} else
#endif
	switch (cur_sum_nni->num) {
#ifdef SUPPORT_XXHASH
	  case CSUM_XXH64:
		XXH64_update(xxh64_state, p, len);
		break;
#endif
#ifdef SUPPORT_XXH3
	  case CSUM_XXH3_64:
		XXH3_64bits_update(xxh3_state, p, len);
		break;
	  case CSUM_XXH3_128:
		XXH3_128bits_update(xxh3_state, p, len);
		break;
#endif
	  case CSUM_MD5:
		md5_update(&ctx_md, (uchar *)p, len);
		break;
	  case CSUM_MD4:
	  case CSUM_MD4_OLD:
	  case CSUM_MD4_BUSTED:
	  case CSUM_MD4_ARCHAIC:
		if (len + sumresidue < CSUM_CHUNK) {
			memcpy(ctx_md.buffer + sumresidue, p, len);
			sumresidue += len;
			break;
		}

		if (sumresidue) {
			int32 i = CSUM_CHUNK - sumresidue;
			memcpy(ctx_md.buffer + sumresidue, p, i);
			mdfour_update(&ctx_md, (uchar *)ctx_md.buffer, CSUM_CHUNK);
			len -= i;
			p += i;
		}

		while (len >= CSUM_CHUNK) {
			mdfour_update(&ctx_md, (uchar *)p, CSUM_CHUNK);
			len -= CSUM_CHUNK;
			p += CSUM_CHUNK;
		}

		sumresidue = len;
		if (sumresidue)
			memcpy(ctx_md.buffer, p, sumresidue);
		break;
	  case CSUM_NONE:
		break;
	  default: /* paranoia to prevent missing case values */
		exit_cleanup(RERR_UNSUPPORTED);
	}
}

/* The sum buffer only needs to be as long as the current checksum's digest
 * len, not MAX_DIGEST_LEN. Note that for CSUM_MD4_ARCHAIC that is the full
 * MD4_DIGEST_LEN even if the file-list code is going to ignore all but the
 * first 2 bytes of it. */
void sum_end(char *sum)
{
#ifdef USE_OPENSSL
	if (cur_sum_evp_md) {
		EVP_DigestFinal_ex(ctx_evp, (uchar *)sum, NULL);
	} else
#endif
	switch (cur_sum_nni->num) {
#ifdef SUPPORT_XXHASH
	  case CSUM_XXH64:
		SIVAL64(sum, 0, XXH64_digest(xxh64_state));
		break;
#endif
#ifdef SUPPORT_XXH3
	  case CSUM_XXH3_64:
		SIVAL64(sum, 0, XXH3_64bits_digest(xxh3_state));
		break;
	  case CSUM_XXH3_128: {
		XXH128_hash_t digest = XXH3_128bits_digest(xxh3_state);
		SIVAL64(sum, 0, digest.low64);
		SIVAL64(sum, 8, digest.high64);
		break;
	  }
#endif
	  case CSUM_MD5:
		md5_result(&ctx_md, (uchar *)sum);
		break;
	  case CSUM_MD4:
	  case CSUM_MD4_OLD:
		mdfour_update(&ctx_md, (uchar *)ctx_md.buffer, sumresidue);
		mdfour_result(&ctx_md, (uchar *)sum);
		break;
	  case CSUM_MD4_BUSTED:
	  case CSUM_MD4_ARCHAIC:
		if (sumresidue)
			mdfour_update(&ctx_md, (uchar *)ctx_md.buffer, sumresidue);
		mdfour_result(&ctx_md, (uchar *)sum);
		break;
	  case CSUM_NONE:
		*sum = '\0';
		break;
	  default: /* paranoia to prevent missing case values */
		exit_cleanup(RERR_UNSUPPORTED);
	}
}

#if defined SUPPORT_XXH3 || defined USE_OPENSSL
static void verify_digest(struct name_num_item *nni, BOOL check_auth_list)
{
#ifdef SUPPORT_XXH3
	static int xxh3_result = 0;
#endif
#ifdef USE_OPENSSL
	static int prior_num = 0, prior_flags = 0, prior_result = 0;
#endif

#ifdef SUPPORT_XXH3
	if (nni->num == CSUM_XXH3_64 || nni->num == CSUM_XXH3_128) {
		if (!xxh3_result) {
			char buf[32816];
			int j;
			for (j = 0; j < (int)sizeof buf; j++)
				buf[j] = ' ' + (j % 96);
			sum_init(nni, 0);
			sum_update(buf, 32816);
			sum_update(buf, 31152);
			sum_update(buf, 32474);
			sum_update(buf, 9322);
			xxh3_result = XXH3_64bits_digest(xxh3_state) != 0xadbcf16d4678d1de ? -1 : 1;
		}
		if (xxh3_result < 0)
			nni->num = CSUM_gone;
		return;
	}
#endif

#ifdef USE_OPENSSL
	if (BITS_SETnUNSET(nni->flags, NNI_EVP, NNI_BUILTIN|NNI_EVP_OK)) {
		if (nni->num == prior_num && nni->flags == prior_flags) {
			nni->flags = prior_result;
			if (!(nni->flags & NNI_EVP))
				nni->num = CSUM_gone;
		} else {
			prior_num = nni->num;
			prior_flags = nni->flags;
			if (!csum_evp_md(nni))
				nni->num = CSUM_gone;
			prior_result = nni->flags;
			if (check_auth_list && (nni = get_nni_by_num(&valid_auth_checksums, prior_num)) != NULL)
				verify_digest(nni, False);
		}
	}
#endif
}
#endif

void init_checksum_choices(void)
{
#if defined SUPPORT_XXH3 || defined USE_OPENSSL
	struct name_num_item *nni;
#endif

	if (initialized_choices)
		return;

#if defined USE_OPENSSL && OPENSSL_VERSION_NUMBER < 0x10100000L
	OpenSSL_add_all_algorithms();
#endif

#if defined SUPPORT_XXH3 || defined USE_OPENSSL
	for (nni = valid_checksums.list; nni->name; nni++)
		verify_digest(nni, True);

	for (nni = valid_auth_checksums.list; nni->name; nni++)
		verify_digest(nni, False);
#endif

	initialized_choices = 1;
}
/*
 * Implement the core of the --chmod option.
 *
 * Copyright (C) 2002 Scott Howard
 * Copyright (C) 2005-2020 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"
#include "itypes.h"

extern mode_t orig_umask;

#define FLAG_X_KEEP (1<<0)
#define FLAG_DIRS_ONLY (1<<1)
#define FLAG_FILES_ONLY (1<<2)

struct chmod_mode_struct {
	struct chmod_mode_struct *next;
	int ModeAND, ModeOR;
	char flags;
};

#define CHMOD_ADD 1
#define CHMOD_SUB 2
#define CHMOD_EQ  3
#define CHMOD_SET 4

#define STATE_ERROR 0
#define STATE_1ST_HALF 1
#define STATE_2ND_HALF 2
#define STATE_OCTAL_NUM 3

/* Parse a chmod-style argument, and break it down into one or more AND/OR
 * pairs in a linked list.  We return a pointer to new items on success
 * (appending the items to the specified list), or NULL on error. */
struct chmod_mode_struct *parse_chmod(const char *modestr,
				      struct chmod_mode_struct **root_mode_ptr)
{
	int state = STATE_1ST_HALF;
	int where = 0, what = 0, op = 0, topbits = 0, topoct = 0, flags = 0;
	struct chmod_mode_struct *first_mode = NULL, *curr_mode = NULL,
				 *prev_mode = NULL;

	while (state != STATE_ERROR) {
		if (!*modestr || *modestr == ',') {
			int bits;

			if (!op) {
				state = STATE_ERROR;
				break;
			}
			prev_mode = curr_mode;
			curr_mode = new_array(struct chmod_mode_struct, 1);
			if (prev_mode)
				prev_mode->next = curr_mode;
			else
				first_mode = curr_mode;
			curr_mode->next = NULL;

			if (where)
				bits = where * what;
			else {
				where = 0111;
				bits = (where * what) & ~orig_umask;
			}

			switch (op) {
			case CHMOD_ADD:
				curr_mode->ModeAND = CHMOD_BITS;
				curr_mode->ModeOR  = bits + topoct;
				break;
			case CHMOD_SUB:
				curr_mode->ModeAND = CHMOD_BITS - bits - topoct;
				curr_mode->ModeOR  = 0;
				break;
			case CHMOD_EQ:
				curr_mode->ModeAND = CHMOD_BITS - (where * 7) - (topoct ? topbits : 0);
				curr_mode->ModeOR  = bits + topoct;
				break;
			case CHMOD_SET:
				curr_mode->ModeAND = 0;
				curr_mode->ModeOR  = bits;
				break;
			}

			curr_mode->flags = flags;

			if (!*modestr)
				break;
			modestr++;

			state = STATE_1ST_HALF;
			where = what = op = topoct = topbits = flags = 0;
		}

		switch (state) {
		case STATE_1ST_HALF:
			switch (*modestr) {
			case 'D':
				if (flags & FLAG_FILES_ONLY)
					state = STATE_ERROR;
				flags |= FLAG_DIRS_ONLY;
				break;
			case 'F':
				if (flags & FLAG_DIRS_ONLY)
					state = STATE_ERROR;
				flags |= FLAG_FILES_ONLY;
				break;
			case 'u':
				where |= 0100;
				topbits |= 04000;
				break;
			case 'g':
				where |= 0010;
				topbits |= 02000;
				break;
			case 'o':
				where |= 0001;
				break;
			case 'a':
				where |= 0111;
				break;
			case '+':
				op = CHMOD_ADD;
				state = STATE_2ND_HALF;
				break;
			case '-':
				op = CHMOD_SUB;
				state = STATE_2ND_HALF;
				break;
			case '=':
				op = CHMOD_EQ;
				state = STATE_2ND_HALF;
				break;
			default:
				if (isDigit(modestr) && *modestr < '8' && !where) {
					op = CHMOD_SET;
					state =  STATE_OCTAL_NUM;
					where = 1;
					what = *modestr - '0';
				} else
					state = STATE_ERROR;
				break;
			}
			break;
		case STATE_2ND_HALF:
			switch (*modestr) {
			case 'r':
				what |= 4;
				break;
			case 'w':
				what |= 2;
				break;
			case 'X':
				flags |= FLAG_X_KEEP;
				/* FALL THROUGH */
			case 'x':
				what |= 1;
				break;
			case 's':
				if (topbits)
					topoct |= topbits;
				else
					topoct = 04000;
				break;
			case 't':
				topoct |= 01000;
				break;
			default:
				state = STATE_ERROR;
				break;
			}
			break;
		case STATE_OCTAL_NUM:
			if (isDigit(modestr) && *modestr < '8') {
				what = what*8 + *modestr - '0';
				if (what > CHMOD_BITS)
					state = STATE_ERROR;
			} else
				state = STATE_ERROR;
			break;
		}
		modestr++;
	}

	if (state == STATE_ERROR) {
		free_chmod_mode(first_mode);
		return NULL;
	}

	if (!(curr_mode = *root_mode_ptr))
		*root_mode_ptr = first_mode;
	else {
		while (curr_mode->next)
			curr_mode = curr_mode->next;
		curr_mode->next = first_mode;
	}

	return first_mode;
}


/* Takes an existing file permission and a list of AND/OR changes, and
 * create a new permissions. */
int tweak_mode(int mode, struct chmod_mode_struct *chmod_modes)
{
	int IsX = mode & 0111;
	int NonPerm = mode & ~CHMOD_BITS;

	for ( ; chmod_modes; chmod_modes = chmod_modes->next) {
		if ((chmod_modes->flags & FLAG_DIRS_ONLY) && !S_ISDIR(NonPerm))
			continue;
		if ((chmod_modes->flags & FLAG_FILES_ONLY) && S_ISDIR(NonPerm))
			continue;
		mode &= chmod_modes->ModeAND;
		if ((chmod_modes->flags & FLAG_X_KEEP) && !IsX && !S_ISDIR(NonPerm))
			mode |= chmod_modes->ModeOR & ~0111;
		else
			mode |= chmod_modes->ModeOR;
	}

	return mode | NonPerm;
}

/* Free the linked list created by parse_chmod. */
int free_chmod_mode(struct chmod_mode_struct *chmod_modes)
{
	struct chmod_mode_struct *next;

	while (chmod_modes) {
		next = chmod_modes->next;
		free(chmod_modes);
		chmod_modes = next;
	}
	return 0;
}
/*
 * End-of-run cleanup routines.
 *
 * Copyright (C) 1996-2000 Andrew Tridgell
 * Copyright (C) 1996 Paul Mackerras
 * Copyright (C) 2002 Martin Pool
 * Copyright (C) 2003-2020 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"

extern int dry_run;
extern int am_server;
extern int am_daemon;
extern int am_receiver;
extern int am_sender;
extern int io_error;
extern int keep_partial;
extern int got_xfer_error;
extern int protocol_version;
extern int output_needs_newline;
extern char *partial_dir;
extern char *logfile_name;

int called_from_signal_handler = 0;
BOOL shutting_down = False;
BOOL flush_ok_after_signal = False;

#ifdef HAVE_SIGACTION
static struct sigaction sigact;
#endif

/**
 * Close all open sockets and files, allowing a (somewhat) graceful
 * shutdown() of socket connections.  This eliminates the abortive
 * TCP RST sent by a Winsock-based system when the close() occurs.
 **/
void close_all(void)
{
#ifdef SHUTDOWN_ALL_SOCKETS
	int max_fd;
	int fd;
	int ret;
	STRUCT_STAT st;

	max_fd = sysconf(_SC_OPEN_MAX) - 1;
	for (fd = max_fd; fd >= 0; fd--) {
		if ((ret = do_fstat(fd, &st)) == 0) {
			if (is_a_socket(fd))
				ret = shutdown(fd, 2);
			ret = close(fd);
		}
	}
#endif
}

/**
 * @file cleanup.c
 *
 * Code for handling interrupted transfers.  Depending on the @c
 * --partial option, we may either delete the temporary file, or go
 * ahead and overwrite the destination.  This second behaviour only
 * occurs if we've sent literal data and therefore hopefully made
 * progress on the transfer.
 **/

/**
 * Set to True once literal data has been sent across the link for the
 * current file. (????)
 *
 * Handling the cleanup when a transfer is interrupted is tricky when
 * --partial is selected.  We need to ensure that the partial file is
 * kept if any real data has been transferred.
 **/
int cleanup_got_literal = 0;

static const char *cleanup_fname;
static const char *cleanup_new_fname;
static struct file_struct *cleanup_file;
static int cleanup_fd_r = -1, cleanup_fd_w = -1;
static pid_t cleanup_pid = 0;

pid_t cleanup_child_pid = -1;

/**
 * Eventually calls exit(), passing @p code, therefore does not return.
 *
 * @param code one of the RERR_* codes from errcode.h.
 **/
NORETURN void _exit_cleanup(int code, const char *file, int line)
{
	static int switch_step = 0;
	static int exit_code = 0, exit_line = 0;
	static const char *exit_file = NULL;
	static int first_code = 0;

	SIGACTION(SIGUSR1, SIG_IGN);
	SIGACTION(SIGUSR2, SIG_IGN);

	if (!exit_code) { /* Preserve first error exit info when recursing. */
		exit_code = code;
		exit_file = file;
		exit_line = line < 0 ? -line : line;
	}

	/* If this is the exit at the end of the run, the server side
	 * should not attempt to output a message (see log_exit()). */
	if (am_server && code == 0)
		am_server = 2;

	/* Some of our actions might cause a recursive call back here, so we
	 * keep track of where we are in the cleanup and never repeat a step. */
	switch (switch_step) {
#include "case_N.h" /* case 0: */
		switch_step++;

		first_code = code;

		if (output_needs_newline) {
			fputc('\n', stdout);
			output_needs_newline = 0;
		}

		if (DEBUG_GTE(EXIT, 2)) {
			rprintf(FINFO,
				"[%s] _exit_cleanup(code=%d, file=%s, line=%d): entered\n",
				who_am_i(), code, src_file(file), line);
		}

#include "case_N.h"
		switch_step++;

		if (cleanup_child_pid != -1) {
			int status;
			int pid = wait_process(cleanup_child_pid, &status, WNOHANG);
			if (pid == cleanup_child_pid) {
				status = WEXITSTATUS(status);
				if (status > exit_code)
					exit_code = status;
			}
		}

#include "case_N.h"
		switch_step++;

		if (cleanup_got_literal && (cleanup_fname || cleanup_fd_w != -1)) {
			if (cleanup_fd_r != -1) {
				close(cleanup_fd_r);
				cleanup_fd_r = -1;
			}
			if (cleanup_fd_w != -1) {
				flush_write_file(cleanup_fd_w);
				close(cleanup_fd_w);
				cleanup_fd_w = -1;
			}
			if (cleanup_fname && cleanup_new_fname && keep_partial
			 && handle_partial_dir(cleanup_new_fname, PDIR_CREATE)) {
				int tweak_modtime = 0;
				const char *fname = cleanup_fname;
				cleanup_fname = NULL;
				if (!partial_dir) {
					/* We don't want to leave a partial file with a modern time or it
					 * could be skipped via --update.  Setting the time to something
					 * really old also helps it to stand out as unfinished in an ls. */
					tweak_modtime = 1;
					cleanup_file->modtime = 0;
				}
				finish_transfer(cleanup_new_fname, fname, NULL, NULL,
						cleanup_file, tweak_modtime, !partial_dir);
			}
		}

#include "case_N.h"
		switch_step++;

		if (flush_ok_after_signal) {
			flush_ok_after_signal = False;
			if (code == RERR_SIGNAL)
				io_flush(FULL_FLUSH);
		}
		if (!exit_code && !code)
			io_flush(FULL_FLUSH);

#include "case_N.h"
		switch_step++;

		if (cleanup_fname)
			do_unlink(cleanup_fname);
		if (exit_code)
			kill_all(SIGUSR1);
		if (cleanup_pid && cleanup_pid == getpid()) {
			char *pidf = lp_pid_file();
			if (pidf && *pidf)
				unlink(lp_pid_file());
		}

		if (exit_code == 0) {
			if (code)
				exit_code = code;
			if (io_error & IOERR_DEL_LIMIT)
				exit_code = RERR_DEL_LIMIT;
			if (io_error & IOERR_VANISHED)
				exit_code = RERR_VANISHED;
			if (io_error & IOERR_GENERAL || got_xfer_error)
				exit_code = RERR_PARTIAL;
		}

		/* If line < 0, this exit is after a MSG_ERROR_EXIT event, so
		 * we don't want to output a duplicate error. */
		if ((exit_code && line > 0)
		 || am_daemon || (logfile_name && (am_server || !INFO_GTE(STATS, 1)))) {
			log_exit(exit_code, exit_file, exit_line);
		}

#include "case_N.h"
		switch_step++;

		if (DEBUG_GTE(EXIT, 1)) {
			rprintf(FINFO,
				"[%s] _exit_cleanup(code=%d, file=%s, line=%d): "
				"about to call exit(%d)%s\n",
				who_am_i(), first_code, exit_file, exit_line, exit_code,
				dry_run ? " (DRY RUN)" : "");
		}

#include "case_N.h"
		switch_step++;

		if (exit_code && exit_code != RERR_SOCKETIO && exit_code != RERR_STREAMIO && exit_code != RERR_SIGNAL1
		 && exit_code != RERR_TIMEOUT && !shutting_down) {
			if (protocol_version >= 31 || am_receiver) {
				if (line > 0) {
					if (DEBUG_GTE(EXIT, 3)) {
						rprintf(FINFO, "[%s] sending MSG_ERROR_EXIT with exit_code %d\n",
							who_am_i(), exit_code);
					}
					send_msg_int(MSG_ERROR_EXIT, exit_code);
				}
				if (!am_sender)
					io_flush(MSG_FLUSH); /* Be sure to send all messages */
				noop_io_until_death();
			}
			else if (!am_sender)
				io_flush(MSG_FLUSH); /* Be sure to send all messages */
		}

#include "case_N.h"
		switch_step++;

		if (am_server && exit_code)
			msleep(100);
		close_all();

		/* FALLTHROUGH */
	default:
		break;
	}

	if (called_from_signal_handler)
		_exit(exit_code);
	exit(exit_code);
}

void cleanup_disable(void)
{
	cleanup_fname = cleanup_new_fname = NULL;
	cleanup_fd_r = cleanup_fd_w = -1;
	cleanup_got_literal = 0;
}


void cleanup_set(const char *fnametmp, const char *fname, struct file_struct *file,
		 int fd_r, int fd_w)
{
	cleanup_fname = fnametmp;
	cleanup_new_fname = fname; /* can be NULL on a partial-dir failure */
	cleanup_file = file;
	cleanup_fd_r = fd_r;
	cleanup_fd_w = fd_w;
}

void cleanup_set_pid(pid_t pid)
{
	cleanup_pid = pid;
}
/*
 * Functions for looking up the remote name or addr of a socket.
 *
 * Copyright (C) 1992-2001 Andrew Tridgell <tridge@samba.org>
 * Copyright (C) 2001, 2002 Martin Pool <mbp@samba.org>
 * Copyright (C) 2002-2022 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

/*
 * This file is now converted to use the new-style getaddrinfo()
 * interface, which supports IPv6 but is also supported on recent
 * IPv4-only machines.  On systems that don't have that interface, we
 * emulate it using the KAME implementation.
 */

#include "rsync.h"
#include "itypes.h"

extern int am_daemon;

static const char default_name[] = "UNKNOWN";
static const char proxyv2sig[] = "\r\n\r\n\0\r\nQUIT\n";

static char ipaddr_buf[100];

#define PROXY_V2_SIG_SIZE ((int)sizeof proxyv2sig - 1)
#define PROXY_V2_HEADER_SIZE (PROXY_V2_SIG_SIZE + 1 + 1 + 2)

#define CMD_LOCAL 0
#define CMD_PROXY 1

#define PROXY_FAM_TCPv4 0x11
#define PROXY_FAM_TCPv6 0x21

#define GET_SOCKADDR_FAMILY(ss) ((struct sockaddr*)ss)->sa_family

static void client_sockaddr(int fd, struct sockaddr_storage *ss, socklen_t *ss_len);
static int check_name(const char *ipaddr, const struct sockaddr_storage *ss, char *name_buf, size_t name_buf_size);
static int valid_ipaddr(const char *s, int allow_scope);

/* Return the IP addr of the client as a string. */
char *client_addr(int fd)
{
	struct sockaddr_storage ss;
	socklen_t length = sizeof ss;

	if (*ipaddr_buf)
		return ipaddr_buf;

	if (am_daemon < 0) {	/* daemon over --rsh mode */
		char *env_str;
		strlcpy(ipaddr_buf, "0.0.0.0", sizeof ipaddr_buf);
		if ((env_str = getenv("REMOTE_HOST")) != NULL
		 || (env_str = getenv("SSH_CONNECTION")) != NULL
		 || (env_str = getenv("SSH_CLIENT")) != NULL
		 || (env_str = getenv("SSH2_CLIENT")) != NULL) {
			char *p;
			strlcpy(ipaddr_buf, env_str, sizeof ipaddr_buf);
			/* Truncate the value to just the IP address. */
			if ((p = strchr(ipaddr_buf, ' ')) != NULL)
				*p = '\0';
		}
		if (valid_ipaddr(ipaddr_buf, True))
			return ipaddr_buf;
	}

	client_sockaddr(fd, &ss, &length);
	getnameinfo((struct sockaddr *)&ss, length, ipaddr_buf, sizeof ipaddr_buf, NULL, 0, NI_NUMERICHOST);

	return ipaddr_buf;
}


/**
 * Return the DNS name of the client.
 *
 * The name is statically cached so that repeated lookups are quick,
 * so there is a limit of one lookup per customer.
 *
 * If anything goes wrong, including the name->addr->name check, then
 * we just use "UNKNOWN", so you can use that value in hosts allow
 * lines.
 *
 * After translation from sockaddr to name we do a forward lookup to
 * make sure nobody is spoofing PTR records.
 **/
char *client_name(const char *ipaddr)
{
	static char name_buf[100];
	char port_buf[100];
	struct sockaddr_storage ss;
	socklen_t ss_len;
	struct addrinfo hint, *answer;
	int err;

	if (*name_buf)
		return name_buf;

	strlcpy(name_buf, default_name, sizeof name_buf);

	if (strcmp(ipaddr, "0.0.0.0") == 0)
		return name_buf;

	memset(&ss, 0, sizeof ss);
	memset(&hint, 0, sizeof hint);

#ifdef AI_NUMERICHOST
	hint.ai_flags = AI_NUMERICHOST;
#endif
	hint.ai_socktype = SOCK_STREAM;

	if ((err = getaddrinfo(ipaddr, NULL, &hint, &answer)) != 0) {
		rprintf(FLOG, "malformed address %s: %s\n", ipaddr, gai_strerror(err));
		return name_buf;
	}

	switch (answer->ai_family) {
	case AF_INET:
		ss_len = sizeof (struct sockaddr_in);
		memcpy(&ss, answer->ai_addr, ss_len);
		break;
#ifdef INET6
	case AF_INET6:
		ss_len = sizeof (struct sockaddr_in6);
		memcpy(&ss, answer->ai_addr, ss_len);
		break;
#endif
	default:
		NOISY_DEATH("Unknown ai_family value");
	}
	freeaddrinfo(answer);

	/* reverse lookup */
	err = getnameinfo((struct sockaddr*)&ss, ss_len, name_buf, sizeof name_buf,
			  port_buf, sizeof port_buf, NI_NAMEREQD | NI_NUMERICSERV);
	if (err) {
		strlcpy(name_buf, default_name, sizeof name_buf);
		rprintf(FLOG, "name lookup failed for %s: %s\n", ipaddr, gai_strerror(err));
	} else
		check_name(ipaddr, &ss, name_buf, sizeof name_buf);

	return name_buf;
}


/* Try to read a proxy protocol header (V1 or V2). Returns 1 on success or 0 on failure. */
int read_proxy_protocol_header(int fd)
{
	union {
		struct {
			char line[108];
		} v1;
		struct {
			char sig[PROXY_V2_SIG_SIZE];
			char ver_cmd;
			char fam;
			char len[2];
			union {
				struct {
					char src_addr[4];
					char dst_addr[4];
					char src_port[2];
					char dst_port[2];
				} ip4;
				struct {
					char src_addr[16];
					char dst_addr[16];
					char src_port[2];
					char dst_port[2];
				} ip6;
				struct {
					char src_addr[108];
					char dst_addr[108];
				} unx;
			} addr;
		} v2;
	} hdr;

	read_buf(fd, (char*)&hdr, PROXY_V2_SIG_SIZE);

	if (memcmp(hdr.v2.sig, proxyv2sig, PROXY_V2_SIG_SIZE) == 0) { /* Proxy V2 */
		int ver, cmd, size;

		read_buf(fd, (char*)&hdr + PROXY_V2_SIG_SIZE, PROXY_V2_HEADER_SIZE - PROXY_V2_SIG_SIZE);

		ver = (hdr.v2.ver_cmd & 0xf0) >> 4;
		cmd = (hdr.v2.ver_cmd & 0x0f);
		size = (hdr.v2.len[0] << 8) + hdr.v2.len[1];

		if (ver != 2 || size + PROXY_V2_HEADER_SIZE > (int)sizeof hdr)
			return 0;

		/* Grab all the remaining data in the binary request. */
		read_buf(fd, (char*)&hdr + PROXY_V2_HEADER_SIZE, size);

		switch (cmd) {
		case CMD_PROXY:
			switch (hdr.v2.fam) {
			case PROXY_FAM_TCPv4:
				if (size != sizeof hdr.v2.addr.ip4)
					return 0;
				inet_ntop(AF_INET, hdr.v2.addr.ip4.src_addr, ipaddr_buf, sizeof ipaddr_buf);
				return valid_ipaddr(ipaddr_buf, False);
#ifdef INET6
			case PROXY_FAM_TCPv6:
				if (size != sizeof hdr.v2.addr.ip6)
					return 0;
				inet_ntop(AF_INET6, hdr.v2.addr.ip6.src_addr, ipaddr_buf, sizeof ipaddr_buf);
				return valid_ipaddr(ipaddr_buf, False);
#endif
			default:
				break;
			}
			/* For an unsupported protocol we'll ignore the proxy data (leaving ipaddr_buf unset)
			 * and accept the connection, which will get handled as a normal socket addr. */
			return 1;
		case CMD_LOCAL:
			return 1;
		default:
			break;
		}

		return 0;
	}

	if (memcmp(hdr.v1.line, "PROXY", 5) == 0) { /* Proxy V1 */
		char *endc, *sp, *p = hdr.v1.line + PROXY_V2_SIG_SIZE;
		int port_chk;

		*p = '\0';
		if (!strchr(hdr.v1.line, '\n')) {
			while (1) {
				read_buf(fd, p, 1);
				if (*p++ == '\n')
					break;
				if (p - hdr.v1.line >= (int)sizeof hdr.v1.line - 1)
					return 0;
			}
			*p = '\0';
		}

		endc = strchr(hdr.v1.line, '\r');
		if (!endc || endc[1] != '\n' || endc[2])
			return 0;
		*endc = '\0';

		p = hdr.v1.line + 5;

		if (!isSpace(p++))
			return 0;
		if (strncmp(p, "TCP4", 4) == 0)
			p += 4;
		else if (strncmp(p, "TCP6", 4) == 0)
			p += 4;
		else if (strncmp(p, "UNKNOWN", 7) == 0)
			return 1;
		else
			return 0;

		if (!isSpace(p++))
			return 0;

		if ((sp = strchr(p, ' ')) == NULL)
			return 0;
		*sp = '\0';
		if (!valid_ipaddr(p, False))
			return 0;
		strlcpy(ipaddr_buf, p, sizeof ipaddr_buf); /* It will always fit when valid. */

		p = sp + 1;
		if ((sp = strchr(p, ' ')) == NULL)
			return 0;
		*sp = '\0';
		if (!valid_ipaddr(p, False))
			return 0;
		/* Ignore destination address. */

		p = sp + 1;
		if ((sp = strchr(p, ' ')) == NULL)
			return 0;
		*sp = '\0';
		port_chk = strtol(p, &endc, 10);
		if (*endc || port_chk == 0)
			return 0;
		/* Ignore source port. */

		p = sp + 1;
		port_chk = strtol(p, &endc, 10);
		if (*endc || port_chk == 0)
			return 0;
		/* Ignore destination port. */

		return 1;
	}

	return 0;
}


/**
 * Get the sockaddr for the client.
 *
 * If it comes in as an ipv4 address mapped into IPv6 format then we
 * convert it back to a regular IPv4.
 **/
static void client_sockaddr(int fd, struct sockaddr_storage *ss, socklen_t *ss_len)
{
	memset(ss, 0, sizeof *ss);

	if (getpeername(fd, (struct sockaddr *) ss, ss_len)) {
		/* FIXME: Can we really not continue? */
		rsyserr(FLOG, errno, "getpeername on fd%d failed", fd);
		exit_cleanup(RERR_SOCKETIO);
	}

#ifdef INET6
	if (GET_SOCKADDR_FAMILY(ss) == AF_INET6
	 && IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)ss)->sin6_addr)) {
		/* OK, so ss is in the IPv6 family, but it is really
		 * an IPv4 address: something like
		 * "::ffff:10.130.1.2".  If we use it as-is, then the
		 * reverse lookup might fail or perhaps something else
		 * bad might happen.  So instead we convert it to an
		 * equivalent address in the IPv4 address family.  */
		struct sockaddr_in6 sin6;
		struct sockaddr_in *sin;

		memcpy(&sin6, ss, sizeof sin6);
		sin = (struct sockaddr_in *)ss;
		memset(sin, 0, sizeof *sin);
		sin->sin_family = AF_INET;
		*ss_len = sizeof (struct sockaddr_in);
#ifdef HAVE_SOCKADDR_IN_LEN
		sin->sin_len = *ss_len;
#endif
		sin->sin_port = sin6.sin6_port;

		/* There is a macro to extract the mapped part
		 * (IN6_V4MAPPED_TO_SINADDR ?), but it does not seem
		 * to be present in the Linux headers. */
		memcpy(&sin->sin_addr, &sin6.sin6_addr.s6_addr[12], sizeof sin->sin_addr);
	}
#endif
}


/**
 * Compare an addrinfo from the resolver to a sockinfo.
 *
 * Like strcmp, returns 0 for identical.
 **/
static int compare_addrinfo_sockaddr(const struct addrinfo *ai, const struct sockaddr_storage *ss)
{
	int ss_family = GET_SOCKADDR_FAMILY(ss);
	const char fn[] = "compare_addrinfo_sockaddr";

	if (ai->ai_family != ss_family) {
		rprintf(FLOG, "%s: response family %d != %d\n",
			fn, ai->ai_family, ss_family);
		return 1;
	}

	/* The comparison method depends on the particular AF. */
	if (ss_family == AF_INET) {
		const struct sockaddr_in *sin1, *sin2;

		sin1 = (const struct sockaddr_in *) ss;
		sin2 = (const struct sockaddr_in *) ai->ai_addr;

		return memcmp(&sin1->sin_addr, &sin2->sin_addr, sizeof sin1->sin_addr);
	}

#ifdef INET6
	if (ss_family == AF_INET6) {
		const struct sockaddr_in6 *sin1, *sin2;

		sin1 = (const struct sockaddr_in6 *) ss;
		sin2 = (const struct sockaddr_in6 *) ai->ai_addr;

		if (ai->ai_addrlen < (int)sizeof (struct sockaddr_in6)) {
			rprintf(FLOG, "%s: too short sockaddr_in6; length=%d\n",
				fn, (int)ai->ai_addrlen);
			return 1;
		}

		if (memcmp(&sin1->sin6_addr, &sin2->sin6_addr, sizeof sin1->sin6_addr))
			return 1;

#ifdef HAVE_SOCKADDR_IN6_SCOPE_ID
		if (sin1->sin6_scope_id != sin2->sin6_scope_id)
			return 1;
#endif
		return 0;
	}
#endif /* INET6 */

	/* don't know */
	return 1;
}


/**
 * Do a forward lookup on @p name_buf and make sure it corresponds to
 * @p ss -- otherwise we may be being spoofed.  If we suspect we are,
 * then we don't abort the connection but just emit a warning, and
 * change @p name_buf to be "UNKNOWN".
 *
 * We don't do anything with the service when checking the name,
 * because it doesn't seem that it could be spoofed in any way, and
 * getaddrinfo on random service names seems to cause problems on AIX.
 **/
static int check_name(const char *ipaddr, const struct sockaddr_storage *ss, char *name_buf, size_t name_buf_size)
{
	struct addrinfo hints, *res, *res0;
	int error;
	int ss_family = GET_SOCKADDR_FAMILY(ss);

	memset(&hints, 0, sizeof hints);
	hints.ai_family = ss_family;
	hints.ai_flags = AI_CANONNAME;
	hints.ai_socktype = SOCK_STREAM;
	error = getaddrinfo(name_buf, NULL, &hints, &res0);
	if (error) {
		rprintf(FLOG, "forward name lookup for %s failed: %s\n",
			name_buf, gai_strerror(error));
		strlcpy(name_buf, default_name, name_buf_size);
		return error;
	}

	/* Given all these results, we expect that one of them will be
	 * the same as ss.  The comparison is a bit complicated. */
	for (res = res0; res; res = res->ai_next) {
		if (!compare_addrinfo_sockaddr(res, ss))
			break;	/* OK, identical */
	}

	if (!res0) {
		/* We hit the end of the list without finding an
		 * address that was the same as ss. */
		rprintf(FLOG, "no known address for \"%s\": "
			"spoofed address?\n", name_buf);
		strlcpy(name_buf, default_name, name_buf_size);
	} else if (res == NULL) {
		/* We hit the end of the list without finding an
		 * address that was the same as ss. */
		rprintf(FLOG, "%s is not a known address for \"%s\": "
			"spoofed address?\n", ipaddr, name_buf);
		strlcpy(name_buf, default_name, name_buf_size);
	}

	freeaddrinfo(res0);
	return 0;
}

/* Returns 1 for a valid IPv4 or IPv6 addr, or 0 for a bad one. */
static int valid_ipaddr(const char *s, int allow_scope)
{
	int i;

	if (strchr(s, ':') != NULL) { /* Only IPv6 has a colon. */
		int count, saw_double_colon = 0;
		int ipv4_at_end = 0;

		if (*s == ':') { /* A colon at the start must be a :: */
			if (*++s != ':')
				return 0;
			saw_double_colon = 1;
			s++;
		}

		for (count = 0; count < 8; count++) {
			if (!*s)
				return saw_double_colon;
			if (allow_scope && *s == '%') {
				if (saw_double_colon)
					break;
				return 0;
			}

			if (strchr(s, ':') == NULL && strchr(s, '.') != NULL) {
				if ((!saw_double_colon && count != 6) || (saw_double_colon && count > 6))
					return 0;
				ipv4_at_end = 1;
				break;
			}

			if (!isHexDigit(s++)) /* Need 1-4 hex digits */
				return 0;
			if (isHexDigit(s) && isHexDigit(++s) && isHexDigit(++s) && isHexDigit(++s))
				return 0;

			if (*s == ':') {
				if (!*++s)
					return 0;
				if (*s == ':') {
					if (saw_double_colon)
						return 0;
					saw_double_colon = 1;
					s++;
				}
			}
		}

		if (!ipv4_at_end) {
			if (allow_scope && *s == '%')
				for (s++; isAlNum(s); s++) { }
			return !*s && s[-1] != '%';
		}
	}

	/* IPv4 */
	for (i = 0; i < 4; i++) {
		long n;
		char *end;

		if (i && *s++ != '.')
			return 0;
		n = strtol(s, &end, 10);
		if (n > 255 || n < 0 || end <= s || end > s+3)
			return 0;
		s = end;
	}

	return !*s;
}
/*
 * The socket based protocol for setting up a connection with rsyncd.
 *
 * Copyright (C) 1998-2001 Andrew Tridgell <tridge@samba.org>
 * Copyright (C) 2001-2002 Martin Pool <mbp@samba.org>
 * Copyright (C) 2002-2022 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"
#include "itypes.h"
#include "ifuncs.h"

extern int quiet;
extern int dry_run;
extern int output_motd;
extern int list_only;
extern int am_sender;
extern int am_server;
extern int am_daemon;
extern int am_root;
extern int msgs2stderr;
extern int rsync_port;
extern int protect_args;
extern int ignore_errors;
extern int preserve_xattrs;
extern int kluge_around_eof;
extern int munge_symlinks;
extern int open_noatime;
extern int sanitize_paths;
extern int numeric_ids;
extern int filesfrom_fd;
extern int remote_protocol;
extern int protocol_version;
extern int io_timeout;
extern int no_detach;
extern int write_batch;
extern int old_style_args;
extern int default_af_hint;
extern int logfile_format_has_i;
extern int logfile_format_has_o_or_i;
extern char *bind_address;
extern char *config_file;
extern char *logfile_format;
extern char *files_from;
extern char *tmpdir;
extern char *early_input_file;
extern struct chmod_mode_struct *chmod_modes;
extern filter_rule_list daemon_filter_list;
#ifdef ICONV_OPTION
extern char *iconv_opt;
extern iconv_t ic_send, ic_recv;
#endif
extern uid_t our_uid;
extern gid_t our_gid;

char *auth_user;
char *daemon_auth_choices;
int read_only = 0;
int module_id = -1;
int pid_file_fd = -1;
int early_input_len = 0;
char *early_input = NULL;
pid_t namecvt_pid = 0;
struct chmod_mode_struct *daemon_chmod_modes;

#define EARLY_INPUT_CMD "#early_input="
#define EARLY_INPUT_CMDLEN (sizeof EARLY_INPUT_CMD - 1)

/* module_dirlen is the length of the module_dir string when in daemon
 * mode and module_dir is not "/"; otherwise 0.  (Note that a chroot-
 * enabled module can have a non-"/" module_dir these days.) */
char *module_dir = NULL;
unsigned int module_dirlen = 0;

char *full_module_path;

static int rl_nulls = 0;
static int namecvt_fd_req = -1, namecvt_fd_ans = -1;

#ifdef HAVE_SIGACTION
static struct sigaction sigact;
#endif

static item_list gid_list = EMPTY_ITEM_LIST;

/* Used when "reverse lookup" is off. */
const char undetermined_hostname[] = "UNDETERMINED";

/**
 * Run a client connected to an rsyncd.  The alternative to this
 * function for remote-shell connections is do_cmd().
 *
 * After negotiating which module to use and reading the server's
 * motd, this hands over to client_run().  Telling the server the
 * module will cause it to chroot/setuid/etc.
 *
 * Instead of doing a transfer, the client may at this stage instead
 * get a listing of remote modules and exit.
 *
 * @return -1 for error in startup, or the result of client_run().
 * Either way, it eventually gets passed to exit_cleanup().
 **/
int start_socket_client(char *host, int remote_argc, char *remote_argv[],
			int argc, char *argv[])
{
	int fd, ret;
	char *p, *user = NULL;

	/* This is redundant with code in start_inband_exchange(), but this
	 * short-circuits a problem in the client before we open a socket,
	 * and the extra check won't hurt. */
	if (**remote_argv == '/') {
		rprintf(FERROR,
			"ERROR: The remote path must start with a module name not a /\n");
		return -1;
	}

	if ((p = strrchr(host, '@')) != NULL) {
		user = host;
		host = p+1;
		*p = '\0';
	}

	fd = open_socket_out_wrapped(host, rsync_port, bind_address, default_af_hint);
	if (fd == -1)
		exit_cleanup(RERR_SOCKETIO);

#ifdef ICONV_CONST
	setup_iconv();
#endif

	ret = start_inband_exchange(fd, fd, user, remote_argc, remote_argv);

	return ret ? ret : client_run(fd, fd, -1, argc, argv);
}

static int exchange_protocols(int f_in, int f_out, char *buf, size_t bufsiz, int am_client)
{
	int remote_sub = -1;
	int our_sub = get_subprotocol_version();

	output_daemon_greeting(f_out, am_client);
	if (!am_client) {
		char *motd = lp_motd_file();
		if (motd && *motd) {
			FILE *f = fopen(motd, "r");
			while (f && !feof(f)) {
				int len = fread(buf, 1, bufsiz - 1, f);
				if (len > 0)
					write_buf(f_out, buf, len);
			}
			if (f)
				fclose(f);
			write_sbuf(f_out, "\n");
		}
	}

	/* This strips the \n. */
	if (!read_line_old(f_in, buf, bufsiz, 0)) {
		if (am_client)
			rprintf(FERROR, "rsync: did not see server greeting\n");
		return -1;
	}

	if (sscanf(buf, "@RSYNCD: %d.%d", &remote_protocol, &remote_sub) < 1) {
		if (am_client)
			rprintf(FERROR, "rsync: server sent \"%s\" rather than greeting\n", buf);
		else
			io_printf(f_out, "@ERROR: protocol startup error\n");
		return -1;
	}

	if (remote_sub < 0) {
		if (remote_protocol >= 30) {
			if (am_client)
				rprintf(FERROR, "rsync: the server omitted the subprotocol value: %s\n", buf);
			else
				io_printf(f_out, "@ERROR: your client omitted the subprotocol value: %s\n", buf);
			return -1;
		}
		remote_sub = 0;
	}

	daemon_auth_choices = strchr(buf + 9, ' ');
	if (daemon_auth_choices) {
		char *cp;
		daemon_auth_choices = strdup(daemon_auth_choices + 1);
		if ((cp = strchr(daemon_auth_choices, '\n')) != NULL)
			*cp = '\0';
	} else if (remote_protocol > 31) {
		if (am_client)
			rprintf(FERROR, "rsync: the server omitted the digest name list: %s\n", buf);
		else
			io_printf(f_out, "@ERROR: your client omitted the digest name list: %s\n", buf);
		return -1;
	}

	if (protocol_version > remote_protocol) {
		protocol_version = remote_protocol;
		if (remote_sub)
			protocol_version--;
	} else if (protocol_version == remote_protocol) {
		if (remote_sub != our_sub)
			protocol_version--;
	}
#if SUBPROTOCOL_VERSION != 0
	else if (protocol_version < remote_protocol) {
		if (our_sub)
			protocol_version--;
	}
#endif

	if (protocol_version >= 30)
		rl_nulls = 1;

	return 0;
}

int start_inband_exchange(int f_in, int f_out, const char *user, int argc, char *argv[])
{
	int i, modlen;
	char line[BIGPATHBUFLEN];
	char *sargs[MAX_ARGS];
	int sargc = 0;
	char *p, *modname;

	assert(argc > 0 && *argv != NULL);

	if (**argv == '/') {
		rprintf(FERROR,
			"ERROR: The remote path must start with a module name\n");
		return -1;
	}

	if (!(p = strchr(*argv, '/')))
		modlen = strlen(*argv);
	else
		modlen = p - *argv;

	modname = new_array(char, modlen+1+1); /* room for '/' & '\0' */
	strlcpy(modname, *argv, modlen + 1);
	modname[modlen] = '/';
	modname[modlen+1] = '\0';

	if (!user)
		user = getenv("USER");
	if (!user)
		user = getenv("LOGNAME");

	if (exchange_protocols(f_in, f_out, line, sizeof line, 1) < 0)
		return -1;

	if (early_input_file) {
		STRUCT_STAT st;
		FILE *f = fopen(early_input_file, "rb");
		if (!f || do_fstat(fileno(f), &st) < 0) {
			rsyserr(FERROR, errno, "failed to open %s", early_input_file);
			return -1;
		}
		early_input_len = st.st_size;
		if (early_input_len > (int)sizeof line) {
			rprintf(FERROR, "%s is > %d bytes.\n", early_input_file, (int)sizeof line);
			return -1;
		}
		if (early_input_len > 0) {
			io_printf(f_out, EARLY_INPUT_CMD "%d\n", early_input_len);
			while (early_input_len > 0) {
				int len;
				if (feof(f)) {
					rprintf(FERROR, "Early EOF in %s\n", early_input_file);
					return -1;
				}
				len = fread(line, 1, early_input_len, f);
				if (len > 0) {
					write_buf(f_out, line, len);
					early_input_len -= len;
				}
			}
		}
		fclose(f);
	}

	server_options(sargs, &sargc);

	if (sargc >= MAX_ARGS - 2)
		goto arg_overflow;

	sargs[sargc++] = ".";

	if (!old_style_args)
		snprintf(line, sizeof line, " %.*s/", modlen, modname);

	while (argc > 0) {
		if (sargc >= MAX_ARGS - 1) {
		  arg_overflow:
			rprintf(FERROR, "internal: args[] overflowed in do_cmd()\n");
			exit_cleanup(RERR_SYNTAX);
		}
		if (strncmp(*argv, modname, modlen) == 0 && argv[0][modlen] == '\0')
			sargs[sargc++] = modname; /* we send "modname/" */
		else {
			char *arg = *argv;
			int extra_chars = *arg == '-' ? 2 : 0; /* a leading dash needs a "./" prefix. */
			/* If --old-args was not specified, make sure that the arg won't split at a mod name! */
			if (!old_style_args && (p = strstr(arg, line)) != NULL) {
				do {
					extra_chars += 2;
				} while ((p = strstr(p+1, line)) != NULL);
			}
			if (extra_chars) {
				char *f = arg;
				char *t = arg = new_array(char, strlen(arg) + extra_chars + 1);
				if (*f == '-') {
					*t++ = '.';
					*t++ = '/';
				}
				while (*f) {
					if (*f == ' ' && strncmp(f, line, modlen+2) == 0) {
						*t++ = '[';
						*t++ = *f++;
						*t++ = ']';
					} else
						*t++ = *f++;
				}
				*t = '\0';
			}
			sargs[sargc++] = arg;
		}
		argv++;
		argc--;
	}

	sargs[sargc] = NULL;

	if (DEBUG_GTE(CMD, 1))
		print_child_argv("sending daemon args:", sargs);

	io_printf(f_out, "%.*s\n", modlen, modname);

	/* Old servers may just drop the connection here,
	 rather than sending a proper EXIT command.  Yuck. */
	kluge_around_eof = list_only && protocol_version < 25 ? 1 : 0;

	while (1) {
		if (!read_line_old(f_in, line, sizeof line, 0)) {
			rprintf(FERROR, "rsync: didn't get server startup line\n");
			return -1;
		}

		if (strncmp(line,"@RSYNCD: AUTHREQD ",18) == 0) {
			auth_client(f_out, user, line+18);
			continue;
		}

		if (strcmp(line,"@RSYNCD: OK") == 0)
			break;

		if (strcmp(line,"@RSYNCD: EXIT") == 0) {
			/* This is sent by recent versions of the
			 * server to terminate the listing of modules.
			 * We don't want to go on and transfer
			 * anything; just exit. */
			exit(0);
		}

		if (strncmp(line, "@ERROR", 6) == 0) {
			rprintf(FERROR, "%s\n", line);
			/* This is always fatal; the server will now
			 * close the socket. */
			return -1;
		}

		/* This might be a MOTD line or a module listing, but there is
		 * no way to differentiate it.  The manpage mentions this. */
		if (output_motd)
			rprintf(FINFO, "%s\n", line);
	}
	kluge_around_eof = 0;

	if (rl_nulls) {
		for (i = 0; i < sargc; i++) {
			if (!sargs[i]) /* stop at --secluded-args NULL */
				break;
			write_sbuf(f_out, sargs[i]);
			write_byte(f_out, 0);
		}
		write_byte(f_out, 0);
	} else {
		for (i = 0; i < sargc; i++)
			io_printf(f_out, "%s\n", sargs[i]);
		write_sbuf(f_out, "\n");
	}

	if (protect_args)
		send_protected_args(f_out, sargs);

	if (protocol_version < 23) {
		if (protocol_version == 22 || !am_sender)
			io_start_multiplex_in(f_in);
	}

	free(modname);

	return 0;
}

#if defined HAVE_SETENV || defined HAVE_PUTENV
static int read_arg_from_pipe(int fd, char *buf, int limit)
{
	char *bp = buf, *eob = buf + limit - 1;

	while (1) {
		int got = read(fd, bp, 1);
		if (got != 1) {
			if (got < 0 && errno == EINTR)
				continue;
			return -1;
		}
		if (*bp == '\0')
			break;
		if (bp < eob)
			bp++;
	}
	*bp = '\0';

	return bp - buf;
}
#endif

void set_env_str(const char *var, const char *str)
{
#ifdef HAVE_SETENV
	if (setenv(var, str, 1) < 0)
		out_of_memory("set_env_str");
#else
#ifdef HAVE_PUTENV
	char *mem;
	if (asprintf(&mem, "%s=%s", var, str) < 0)
		out_of_memory("set_env_str");
	putenv(mem);
#else
	(void)var;
	(void)str;
#endif
#endif
}

#if defined HAVE_SETENV || defined HAVE_PUTENV

static void set_envN_str(const char *var, int num, const char *str)
{
#ifdef HAVE_SETENV
	char buf[128];
	(void)snprintf(buf, sizeof buf, "%s%d", var, num);
	if (setenv(buf, str, 1) < 0)
		out_of_memory("set_env_str");
#else
#ifdef HAVE_PUTENV
	char *mem;
	if (asprintf(&mem, "%s%d=%s", var, num, str) < 0)
		out_of_memory("set_envN_str");
	putenv(mem);
#endif
#endif
}

void set_env_num(const char *var, long num)
{
#ifdef HAVE_SETENV
	char val[64];
	(void)snprintf(val, sizeof val, "%ld", num);
	if (setenv(var, val, 1) < 0)
		out_of_memory("set_env_str");
#else
#ifdef HAVE_PUTENV
	char *mem;
	if (asprintf(&mem, "%s=%ld", var, num) < 0)
		out_of_memory("set_env_num");
	putenv(mem);
#endif
#endif
}

/* Used for "early exec", "pre-xfer exec", and the "name converter" script. */
static pid_t start_pre_exec(const char *cmd, int *arg_fd_ptr, int *error_fd_ptr)
{
	int arg_fds[2], error_fds[2], arg_fd;
	pid_t pid;

	if ((error_fd_ptr && pipe(error_fds) < 0) || pipe(arg_fds) < 0 || (pid = fork()) < 0)
		return (pid_t)-1;

	if (pid == 0) {
		char buf[BIGPATHBUFLEN];
		int j, len, status;

		if (error_fd_ptr) {
			close(error_fds[0]);
			set_blocking(error_fds[1]);
		}

		close(arg_fds[1]);
		arg_fd = arg_fds[0];
		set_blocking(arg_fd);

		len = read_arg_from_pipe(arg_fd, buf, BIGPATHBUFLEN);
		if (len <= 0)
			_exit(1);
		set_env_str("RSYNC_REQUEST", buf);

		for (j = 0; ; j++) {
			len = read_arg_from_pipe(arg_fd, buf, BIGPATHBUFLEN);
			if (len <= 0) {
				if (!len)
					break;
				_exit(1);
			}
			set_envN_str("RSYNC_ARG", j, buf);
		}

		dup2(arg_fd, STDIN_FILENO);
		close(arg_fd);

		if (error_fd_ptr) {
			dup2(error_fds[1], STDOUT_FILENO);
			close(error_fds[1]);
		}

		status = shell_exec(cmd);

		if (!WIFEXITED(status))
			_exit(1);
		_exit(WEXITSTATUS(status));
	}

	if (error_fd_ptr) {
		close(error_fds[1]);
		*error_fd_ptr = error_fds[0];
		set_blocking(error_fds[0]);
	}

	close(arg_fds[0]);
	arg_fd = *arg_fd_ptr = arg_fds[1];
	set_blocking(arg_fd);

	return pid;
}

#endif

static void write_pre_exec_args(int write_fd, char *request, char **early_argv, char **argv, int exec_type)
{
	int j = 0;

	if (!request)
		request = "(NONE)";

	write_buf(write_fd, request, strlen(request)+1);
	if (early_argv) {
		for ( ; *early_argv; early_argv++)
			write_buf(write_fd, *early_argv, strlen(*early_argv)+1);
		j = 1; /* Skip arg0 name in argv. */
	}
	if (argv) {
		for ( ; argv[j]; j++)
			write_buf(write_fd, argv[j], strlen(argv[j])+1);
	}
	write_byte(write_fd, 0);

	if (exec_type == 1 && early_input_len)
		write_buf(write_fd, early_input, early_input_len);

	if (exec_type != 2) /* the name converter needs this left open */
		close(write_fd);
}

static char *finish_pre_exec(const char *desc, pid_t pid, int read_fd)
{
	char buf[BIGPATHBUFLEN], *bp, *cr;
	int j, status = -1, msglen = sizeof buf - 1;

	if (read_fd >= 0) {
		/* Read the stdout from the program.  This it is only displayed
		 * to the user if the script also returns an error status. */
		for (bp = buf, cr = buf; msglen > 0; msglen -= j) {
			if ((j = read(read_fd, bp, msglen)) <= 0) {
				if (j == 0)
					break;
				if (errno == EINTR)
					continue;
				break; /* Just ignore the read error for now... */
			}
			bp[j] = '\0';
			while (1) {
				if ((cr = strchr(cr, '\r')) == NULL) {
					cr = bp + j;
					break;
				}
				if (!cr[1])
					break; /* wait for more data before we decide what to do */
				if (cr[1] == '\n') {
					memmove(cr, cr+1, j - (cr - bp));
					j--;
				} else
					cr++;
			}
			bp += j;
		}
		*bp = '\0';

		close(read_fd);
	} else
		*buf = '\0';

	if (wait_process(pid, &status, 0) < 0
	 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) {
		char *e;
		if (asprintf(&e, "%s returned failure (%d)%s%s%s\n%s",
			     desc, status, status < 0 ? ": " : "",
			     status < 0 ? strerror(errno) : "",
			     *buf ? ":" : "", buf) < 0)
			return "out_of_memory in finish_pre_exec\n";
		return e;
	}
	return NULL;
}

static int path_failure(int f_out, const char *dir, BOOL was_chdir)
{
	if (was_chdir)
		rsyserr(FLOG, errno, "chdir %s failed", dir);
	else
		rprintf(FLOG, "normalize_path(%s) failed\n", dir);
	io_printf(f_out, "@ERROR: chdir failed\n");
	return -1;
}

static int add_a_group(int f_out, const char *gname)
{
	gid_t gid, *gid_p;
	if (!group_to_gid(gname, &gid, True)) {
		rprintf(FLOG, "Invalid gid %s\n", gname);
		io_printf(f_out, "@ERROR: invalid gid %s\n", gname);
		return -1;
	}
	gid_p = EXPAND_ITEM_LIST(&gid_list, gid_t, -32);
	*gid_p = gid;
	return 0;
}

#ifdef HAVE_GETGROUPLIST
static int want_all_groups(int f_out, uid_t uid)
{
	const char *err;
	if ((err = getallgroups(uid, &gid_list)) != NULL) {
		rsyserr(FLOG, errno, "%s", err);
		io_printf(f_out, "@ERROR: %s\n", err);
		return -1;
	}
	return 0;
}
#elif defined HAVE_INITGROUPS
static struct passwd *want_all_groups(int f_out, uid_t uid)
{
	struct passwd *pw;
	gid_t *gid_p;
	if ((pw = getpwuid(uid)) == NULL) {
		rsyserr(FLOG, errno, "getpwuid failed");
		io_printf(f_out, "@ERROR: getpwuid failed\n");
		return NULL;
	}
	/* Start with the default group and initgroups() will add the rest. */
	gid_p = EXPAND_ITEM_LIST(&gid_list, gid_t, -32);
	*gid_p = pw->pw_gid;
	return pw;
}
#endif

static int rsync_module(int f_in, int f_out, int i, const char *addr, const char *host)
{
	int argc;
	char **argv, **orig_argv, **orig_early_argv, *module_chdir;
	char line[BIGPATHBUFLEN];
#if defined HAVE_INITGROUPS && !defined HAVE_GETGROUPLIST
	struct passwd *pw = NULL;
#endif
	uid_t uid;
	int set_uid;
	char *p, *err_msg = NULL;
	char *name = lp_name(i);
	int use_chroot = lp_use_chroot(i); /* might be 1 (yes), 0 (no), or -1 (unset) */
	int ret, pre_exec_arg_fd = -1, pre_exec_error_fd = -1;
	int save_munge_symlinks;
	pid_t pre_exec_pid = 0;
	char *request = NULL;

	set_env_str("RSYNC_MODULE_NAME", name);

#ifdef ICONV_OPTION
	iconv_opt = lp_charset(i);
	if (*iconv_opt)
		setup_iconv();
	iconv_opt = NULL;
#endif

	/* If reverse lookup is disabled globally but enabled for this module,
	 * we need to do it now before the access check. */
	if (host == undetermined_hostname && lp_reverse_lookup(i))
		host = client_name(client_addr(f_in));
	set_env_str("RSYNC_HOST_NAME", host);
	set_env_str("RSYNC_HOST_ADDR", addr);

	if (!allow_access(addr, &host, i)) {
		rprintf(FLOG, "rsync denied on module %s from %s (%s)\n",
			name, host, addr);
		if (!lp_list(i))
			io_printf(f_out, "@ERROR: Unknown module '%s'\n", name);
		else {
			io_printf(f_out,
				  "@ERROR: access denied to %s from %s (%s)\n",
				  name, host, addr);
		}
		return -1;
	}

	if (am_daemon > 0) {
		rprintf(FLOG, "rsync allowed access on module %s from %s (%s)\n",
			name, host, addr);
	}

	if (!claim_connection(lp_lock_file(i), lp_max_connections(i))) {
		if (errno) {
			rsyserr(FLOG, errno, "failed to open lock file %s",
				lp_lock_file(i));
			io_printf(f_out, "@ERROR: failed to open lock file\n");
		} else {
			rprintf(FLOG, "max connections (%d) reached\n",
				lp_max_connections(i));
			io_printf(f_out, "@ERROR: max connections (%d) reached -- try again later\n",
				lp_max_connections(i));
		}
		return -1;
	}

	read_only = lp_read_only(i); /* may also be overridden by auth_server() */
	auth_user = auth_server(f_in, f_out, i, host, addr, "@RSYNCD: AUTHREQD ");

	if (!auth_user) {
		io_printf(f_out, "@ERROR: auth failed on module %s\n", name);
		return -1;
	}
	set_env_str("RSYNC_USER_NAME", auth_user);

	module_id = i;

	if (lp_transfer_logging(module_id) && !logfile_format)
		logfile_format = lp_log_format(module_id);
	if (log_format_has(logfile_format, 'i'))
		logfile_format_has_i = 1;
	if (logfile_format_has_i || log_format_has(logfile_format, 'o'))
		logfile_format_has_o_or_i = 1;

	uid = MY_UID();
	am_root = (uid == ROOT_UID);

	p = *lp_uid(module_id) ? lp_uid(module_id) : am_root ? NOBODY_USER : NULL;
	if (p) {
		if (!user_to_uid(p, &uid, True)) {
			rprintf(FLOG, "Invalid uid %s\n", p);
			io_printf(f_out, "@ERROR: invalid uid %s\n", p);
			return -1;
		}
		set_uid = 1;
	} else
		set_uid = 0;

	p = *lp_gid(module_id) ? conf_strtok(lp_gid(module_id)) : NULL;
	if (p) {
		/* The "*" gid must be the first item in the list. */
		if (strcmp(p, "*") == 0) {
#ifdef HAVE_GETGROUPLIST
			if (want_all_groups(f_out, uid) < 0)
				return -1;
#elif defined HAVE_INITGROUPS
			if ((pw = want_all_groups(f_out, uid)) == NULL)
				return -1;
#else
			rprintf(FLOG, "This rsync does not support a gid of \"*\"\n");
			io_printf(f_out, "@ERROR: invalid gid setting.\n");
			return -1;
#endif
		} else if (add_a_group(f_out, p) < 0)
			return -1;
		while ((p = conf_strtok(NULL)) != NULL) {
#if defined HAVE_INITGROUPS && !defined HAVE_GETGROUPLIST
			if (pw) {
				rprintf(FLOG, "This rsync cannot add groups after \"*\".\n");
				io_printf(f_out, "@ERROR: invalid gid setting.\n");
				return -1;
			}
#endif
			if (add_a_group(f_out, p) < 0)
				return -1;
		}
	} else if (am_root) {
		if (add_a_group(f_out, NOBODY_GROUP) < 0)
			return -1;
	}

	module_dir = lp_path(module_id);
	if (*module_dir == '\0') {
		rprintf(FLOG, "No path specified for module %s\n", name);
		io_printf(f_out, "@ERROR: no path setting.\n");
		return -1;
	}
	if (use_chroot < 0) {
		if (strstr(module_dir, "/./") != NULL)
			use_chroot = 1; /* The module is expecting a chroot inner & outer path. */
		else if (chroot("/") < 0) {
			rprintf(FLOG, "chroot test failed: %s. "
				      "Switching 'use chroot' from unset to false.\n",
				      strerror(errno));
			use_chroot = 0;
		} else {
			if (chdir("/") < 0)
			    rsyserr(FLOG, errno, "chdir(\"/\") failed");
			use_chroot = 1;
		}
	}
	if (use_chroot) {
		if ((p = strstr(module_dir, "/./")) != NULL) {
			*p = '\0'; /* Temporary... */
			if (!(module_chdir = normalize_path(module_dir, True, NULL)))
				return path_failure(f_out, module_dir, False);
			*p = '/';
			if (!(p = normalize_path(p + 2, True, &module_dirlen)))
				return path_failure(f_out, strstr(module_dir, "/./"), False);
			if (!(full_module_path = normalize_path(module_dir, False, NULL)))
				full_module_path = module_dir;
			module_dir = p;
		} else {
			if (!(module_chdir = normalize_path(module_dir, False, NULL)))
				return path_failure(f_out, module_dir, False);
			full_module_path = module_chdir;
			module_dir = "/";
			module_dirlen = 1;
		}
	} else {
		if (!(module_chdir = normalize_path(module_dir, False, &module_dirlen)))
			return path_failure(f_out, module_dir, False);
		full_module_path = module_dir = module_chdir;
	}
	set_env_str("RSYNC_MODULE_PATH", full_module_path);

	if (module_dirlen == 1) {
		module_dirlen = 0;
		set_filter_dir("/", 1);
	} else
		set_filter_dir(module_dir, module_dirlen);

	p = lp_filter(module_id);
	parse_filter_str(&daemon_filter_list, p, rule_template(FILTRULE_WORD_SPLIT),
		XFLG_ABS_IF_SLASH | XFLG_DIR2WILD3);

	p = lp_include_from(module_id);
	parse_filter_file(&daemon_filter_list, p, rule_template(FILTRULE_INCLUDE),
		XFLG_ABS_IF_SLASH | XFLG_DIR2WILD3 | XFLG_OLD_PREFIXES | XFLG_FATAL_ERRORS);

	p = lp_include(module_id);
	parse_filter_str(&daemon_filter_list, p,
		rule_template(FILTRULE_INCLUDE | FILTRULE_WORD_SPLIT),
		XFLG_ABS_IF_SLASH | XFLG_DIR2WILD3 | XFLG_OLD_PREFIXES);

	p = lp_exclude_from(module_id);
	parse_filter_file(&daemon_filter_list, p, rule_template(0),
		XFLG_ABS_IF_SLASH | XFLG_DIR2WILD3 | XFLG_OLD_PREFIXES | XFLG_FATAL_ERRORS);

	p = lp_exclude(module_id);
	parse_filter_str(&daemon_filter_list, p, rule_template(FILTRULE_WORD_SPLIT),
		XFLG_ABS_IF_SLASH | XFLG_DIR2WILD3 | XFLG_OLD_PREFIXES);

	log_init(1);

#if defined HAVE_SETENV || defined HAVE_PUTENV
	if ((*lp_early_exec(module_id) || *lp_prexfer_exec(module_id)
	  || *lp_postxfer_exec(module_id) || *lp_name_converter(module_id))
	 && !getenv("RSYNC_NO_XFER_EXEC")) {
		set_env_num("RSYNC_PID", (long)getpid());

		/* For post-xfer exec, fork a new process to run the rsync
		 * daemon while this process waits for the exit status and
		 * runs the indicated command at that point. */
		if (*lp_postxfer_exec(module_id)) {
			pid_t pid = fork();
			if (pid < 0) {
				rsyserr(FLOG, errno, "fork failed");
				io_printf(f_out, "@ERROR: fork failed\n");
				return -1;
			}
			if (pid) {
				int status;
				close(f_in);
				if (f_out != f_in)
					close(f_out);
				if (wait_process(pid, &status, 0) < 0)
					status = -1;
				set_env_num("RSYNC_RAW_STATUS", status);
				if (WIFEXITED(status))
					status = WEXITSTATUS(status);
				else
					status = -1;
				set_env_num("RSYNC_EXIT_STATUS", status);
				if (shell_exec(lp_postxfer_exec(module_id)) < 0)
					status = -1;
				_exit(status);
			}
		}

		/* For early exec, fork a child process to run the indicated
		 * command and wait for it to exit. */
		if (*lp_early_exec(module_id)) {
			int arg_fd;
			pid_t pid = start_pre_exec(lp_early_exec(module_id), &arg_fd, NULL);
			if (pid == (pid_t)-1) {
				rsyserr(FLOG, errno, "early exec preparation failed");
				io_printf(f_out, "@ERROR: early exec preparation failed\n");
				return -1;
			}
			write_pre_exec_args(arg_fd, NULL, NULL, NULL, 1);
			if (finish_pre_exec("early exec", pid, -1) != NULL) {
				rsyserr(FLOG, errno, "early exec failed");
				io_printf(f_out, "@ERROR: early exec failed\n");
				return -1;
			}
		}

		/* For pre-xfer exec, fork a child process to run the indicated
		 * command, though it first waits for the parent process to
		 * send us the user's request via a pipe. */
		if (*lp_prexfer_exec(module_id)) {
			pre_exec_pid = start_pre_exec(lp_prexfer_exec(module_id), &pre_exec_arg_fd, &pre_exec_error_fd);
			if (pre_exec_pid == (pid_t)-1) {
				rsyserr(FLOG, errno, "pre-xfer exec preparation failed");
				io_printf(f_out, "@ERROR: pre-xfer exec preparation failed\n");
				return -1;
			}
		}

		if (*lp_name_converter(module_id)) {
			namecvt_pid = start_pre_exec(lp_name_converter(module_id), &namecvt_fd_req, &namecvt_fd_ans);
			if (namecvt_pid == (pid_t)-1) {
				rsyserr(FLOG, errno, "name-converter exec preparation failed");
				io_printf(f_out, "@ERROR: name-converter exec preparation failed\n");
				return -1;
			}
		}
	}
#endif

	if (early_input) {
		free(early_input);
		early_input = NULL;
	}

	if (use_chroot) {
		if (chroot(module_chdir)) {
			rsyserr(FLOG, errno, "chroot(\"%s\") failed", module_chdir);
			io_printf(f_out, "@ERROR: chroot failed\n");
			return -1;
		}
		module_chdir = module_dir;
	}

	if (!change_dir(module_chdir, CD_NORMAL))
		return path_failure(f_out, module_chdir, True);
	if (module_dirlen)
		sanitize_paths = 1;

	if ((munge_symlinks = lp_munge_symlinks(module_id)) < 0)
		munge_symlinks = !use_chroot || module_dirlen;
	if (munge_symlinks) {
		STRUCT_STAT st;
		char prefix[SYMLINK_PREFIX_LEN]; /* NOT +1 ! */
		strlcpy(prefix, SYMLINK_PREFIX, sizeof prefix); /* trim the trailing slash */
		if (do_stat(prefix, &st) == 0 && S_ISDIR(st.st_mode)) {
			rprintf(FLOG, "Symlink munging is unsafe when a %s directory exists.\n",
				prefix);
			io_printf(f_out, "@ERROR: daemon security issue -- contact admin\n", name);
			exit_cleanup(RERR_UNSUPPORTED);
		}
	}

	if (gid_list.count) {
		gid_t *gid_array = gid_list.items;
		if (setgid(gid_array[0])) {
			rsyserr(FLOG, errno, "setgid %ld failed", (long)gid_array[0]);
			io_printf(f_out, "@ERROR: setgid failed\n");
			return -1;
		}
#ifdef HAVE_SETGROUPS
		/* Set the group(s) we want to be active. */
		if (setgroups(gid_list.count, gid_array)) {
			rsyserr(FLOG, errno, "setgroups failed");
			io_printf(f_out, "@ERROR: setgroups failed\n");
			return -1;
		}
#endif
#if defined HAVE_INITGROUPS && !defined HAVE_GETGROUPLIST
		/* pw is set if the user wants all the user's groups. */
		if (pw && initgroups(pw->pw_name, pw->pw_gid) < 0) {
			rsyserr(FLOG, errno, "initgroups failed");
			io_printf(f_out, "@ERROR: initgroups failed\n");
			return -1;
		}
#endif
		our_gid = MY_GID();
	}

	if (set_uid) {
		if (setuid(uid) < 0
#ifdef HAVE_SETEUID
		 || seteuid(uid) < 0
#endif
		) {
			rsyserr(FLOG, errno, "setuid %ld failed", (long)uid);
			io_printf(f_out, "@ERROR: setuid failed\n");
			return -1;
		}

		our_uid = MY_UID();
		am_root = (our_uid == ROOT_UID);
	}

	if (lp_temp_dir(module_id) && *lp_temp_dir(module_id)) {
		tmpdir = lp_temp_dir(module_id);
		if (strlen(tmpdir) >= MAXPATHLEN - 10) {
			rprintf(FLOG,
				"the 'temp dir' value for %s is WAY too long -- ignoring.\n",
				name);
			tmpdir = NULL;
		}
	}

	io_printf(f_out, "@RSYNCD: OK\n");

	read_args(f_in, name, line, sizeof line, rl_nulls, &argv, &argc, &request);
	orig_argv = argv;

	save_munge_symlinks = munge_symlinks;

	reset_output_levels(); /* future verbosity is controlled by client options */
	ret = parse_arguments(&argc, (const char ***) &argv);
	if (protect_args && ret) {
		orig_early_argv = orig_argv;
		protect_args = 2;
		read_args(f_in, name, line, sizeof line, 1, &argv, &argc, &request);
		orig_argv = argv;
		ret = parse_arguments(&argc, (const char ***) &argv);
	} else
		orig_early_argv = NULL;

	/* The default is to use the user's setting unless the module sets True or False. */
	if (lp_open_noatime(module_id) >= 0)
		open_noatime = lp_open_noatime(module_id);

	munge_symlinks = save_munge_symlinks; /* The client mustn't control this. */

	if (am_daemon > 0)
		msgs2stderr = 0; /* A non-rsh-run daemon doesn't have stderr for msgs. */

	if (pre_exec_pid) {
		write_pre_exec_args(pre_exec_arg_fd, request, orig_early_argv, orig_argv, 0);
		err_msg = finish_pre_exec("pre-xfer exec", pre_exec_pid, pre_exec_error_fd);
	}

	if (namecvt_pid)
		write_pre_exec_args(namecvt_fd_req, request, orig_early_argv, orig_argv, 2);

	if (orig_early_argv)
		free(orig_early_argv);

	am_server = 1; /* Don't let someone try to be tricky. */
	quiet = 0;
	if (lp_ignore_errors(module_id))
		ignore_errors = 1;
	if (write_batch < 0)
		dry_run = 1;

	if (lp_fake_super(module_id)) {
		if (preserve_xattrs > 1)
			preserve_xattrs = 1;
		am_root = -1;
	} else if (am_root < 0) /* Treat --fake-super from client as --super. */
		am_root = 2;

	if (filesfrom_fd == 0)
		filesfrom_fd = f_in;

	if (request) {
		if (*auth_user) {
			rprintf(FLOG, "rsync %s %s from %s@%s (%s)\n",
				am_sender ? "on" : "to",
				request, auth_user, host, addr);
		} else {
			rprintf(FLOG, "rsync %s %s from %s (%s)\n",
				am_sender ? "on" : "to",
				request, host, addr);
		}
		free(request);
	}

#ifndef DEBUG
	/* don't allow the logs to be flooded too fast */
	limit_output_verbosity(lp_max_verbosity(module_id));
#endif

	if (protocol_version < 23 && (protocol_version == 22 || am_sender))
		io_start_multiplex_out(f_out);
	else if (!ret || err_msg) {
		/* We have to get I/O multiplexing started so that we can
		 * get the error back to the client.  This means getting
		 * the protocol setup finished first in later versions. */
		setup_protocol(f_out, f_in);
		if (!am_sender) {
			/* Since we failed in our option parsing, we may not
			 * have finished parsing that the client sent us a
			 * --files-from option, so look for it manually.
			 * Without this, the socket would be in the wrong
			 * state for the upcoming error message. */
			if (!files_from) {
				int i;
				for (i = 0; i < argc; i++) {
					if (strncmp(argv[i], "--files-from", 12) == 0) {
						files_from = "";
						break;
					}
				}
			}
			if (files_from)
				write_byte(f_out, 0);
		}
		io_start_multiplex_out(f_out);
	}

	if (!ret || err_msg) {
		if (err_msg) {
			while ((p = strchr(err_msg, '\n')) != NULL) {
				int len = p - err_msg + 1;
				rwrite(FERROR, err_msg, len, 0);
				err_msg += len;
			}
			if (*err_msg)
				rprintf(FERROR, "%s\n", err_msg);
			io_flush(MSG_FLUSH);
		} else
			option_error();
		msleep(400);
		exit_cleanup(RERR_UNSUPPORTED);
	}

#ifdef ICONV_OPTION
	if (!iconv_opt) {
		if (ic_send != (iconv_t)-1) {
			iconv_close(ic_send);
			ic_send = (iconv_t)-1;
		}
		if (ic_recv != (iconv_t)-1) {
			iconv_close(ic_recv);
			ic_recv = (iconv_t)-1;
		}
	}
#endif

	if (!numeric_ids
	 && (use_chroot ? lp_numeric_ids(module_id) != False && !*lp_name_converter(module_id)
		        : lp_numeric_ids(module_id) == True))
		numeric_ids = -1; /* Set --numeric-ids w/o breaking protocol. */

	if (lp_timeout(module_id) && (!io_timeout || lp_timeout(module_id) < io_timeout))
		set_io_timeout(lp_timeout(module_id));

	/* If we have some incoming/outgoing chmod changes, append them to
	 * any user-specified changes (making our changes have priority).
	 * We also get a pointer to just our changes so that a receiver
	 * process can use them separately if --perms wasn't specified. */
	if (am_sender)
		p = lp_outgoing_chmod(module_id);
	else
		p = lp_incoming_chmod(module_id);
	if (*p && !(daemon_chmod_modes = parse_chmod(p, &chmod_modes))) {
		rprintf(FLOG, "Invalid \"%sing chmod\" directive: %s\n",
			am_sender ? "outgo" : "incom", p);
	}

	start_server(f_in, f_out, argc, argv);

	return 0;
}

BOOL namecvt_call(const char *cmd, const char **name_p, id_t *id_p)
{
	char buf[1024];
	int got, len;

	if (*name_p)
		len = snprintf(buf, sizeof buf, "%s %s\n", cmd, *name_p);
	else
		len = snprintf(buf, sizeof buf, "%s %ld\n", cmd, (long)*id_p);
	if (len >= (int)sizeof buf) {
		rprintf(FERROR, "namecvt_call() request was too large.\n");
		exit_cleanup(RERR_UNSUPPORTED);
	}

	while ((got = write(namecvt_fd_req, buf, len)) != len) {
		if (got < 0 && errno == EINTR)
			continue;
		rprintf(FERROR, "Connection to name-converter failed.\n");
		exit_cleanup(RERR_SOCKETIO);
	}

	if (!read_line_old(namecvt_fd_ans, buf, sizeof buf, 0))
		return False;

	if (*name_p)
		*id_p = (id_t)atol(buf);
	else
		*name_p = strdup(buf);

	return True;
}

/* send a list of available modules to the client. Don't list those
   with "list = False". */
static void send_listing(int fd)
{
	int n = lp_num_modules();
	int i;

	for (i = 0; i < n; i++) {
		if (lp_list(i))
			io_printf(fd, "%-15s\t%s\n", lp_name(i), lp_comment(i));
	}

	if (protocol_version >= 25)
		io_printf(fd,"@RSYNCD: EXIT\n");
}

static int load_config(int globals_only)
{
	if (!config_file) {
		if (am_daemon < 0 && am_root <= 0)
			config_file = RSYNCD_USERCONF;
		else
			config_file = RSYNCD_SYSCONF;
	}
	return lp_load(config_file, globals_only);
}

/* this is called when a connection is established to a client
   and we want to start talking. The setup of the system is done from
   here */
int start_daemon(int f_in, int f_out)
{
	char line[1024];
	const char *addr, *host;
	char *p;
	int i;

	/* At this point, am_server is only set for a daemon started via rsh.
	 * Because am_server gets forced on soon, we'll set am_daemon to -1 as
	 * a flag that can be checked later on to distinguish a normal daemon
	 * from an rsh-run daemon. */
	if (am_server)
		am_daemon = -1;

	io_set_sock_fds(f_in, f_out);

	/* We must load the config file before calling any function that
	 * might cause log-file output to occur.  This ensures that the
	 * "log file" param gets honored for the 2 non-forked use-cases
	 * (when rsync is run by init and run by a remote shell). */
	if (!load_config(0))
		exit_cleanup(RERR_SYNTAX);

	if (lp_proxy_protocol() && !read_proxy_protocol_header(f_in))
		return -1;

	p = lp_daemon_chroot();
	if (*p) {
		log_init(0); /* Make use we've initialized syslog before chrooting. */
		if (chroot(p) < 0) {
			rsyserr(FLOG, errno, "daemon chroot(\"%s\") failed", p);
			return -1;
		}
		if (chdir("/") < 0) {
			rsyserr(FLOG, errno, "daemon chdir(\"/\") failed");
			return -1;
		}
	}
	p = lp_daemon_gid();
	if (*p) {
		gid_t gid;
		if (!group_to_gid(p, &gid, True)) {
			rprintf(FLOG, "Invalid daemon gid: %s\n", p);
			return -1;
		}
		if (setgid(gid) < 0) {
			rsyserr(FLOG, errno, "Unable to set group to daemon gid %ld", (long)gid);
			return -1;
		}
		our_gid = MY_GID();
	}
	p = lp_daemon_uid();
	if (*p) {
		uid_t uid;
		if (!user_to_uid(p, &uid, True)) {
			rprintf(FLOG, "Invalid daemon uid: %s\n", p);
			return -1;
		}
		if (setuid(uid) < 0) {
			rsyserr(FLOG, errno, "Unable to set user to daemon uid %ld", (long)uid);
			return -1;
		}
		our_uid = MY_UID();
		am_root = (our_uid == ROOT_UID);
	}

	addr = client_addr(f_in);
	host = lp_reverse_lookup(-1) ? client_name(addr) : undetermined_hostname;
	rprintf(FLOG, "connect from %s (%s)\n", host, addr);

	if (am_daemon > 0) {
		set_socket_options(f_in, "SO_KEEPALIVE");
		set_nonblocking(f_in);
	}

	if (exchange_protocols(f_in, f_out, line, sizeof line, 0) < 0)
		return -1;

	line[0] = 0;
	if (!read_line_old(f_in, line, sizeof line, 0))
		return -1;

	if (strncmp(line, EARLY_INPUT_CMD, EARLY_INPUT_CMDLEN) == 0) {
		early_input_len = strtol(line + EARLY_INPUT_CMDLEN, NULL, 10);
		if (early_input_len <= 0 || early_input_len > BIGPATHBUFLEN) {
			io_printf(f_out, "@ERROR: invalid early_input length\n");
			return -1;
		}
		early_input = new_array(char, early_input_len);
		read_buf(f_in, early_input, early_input_len);

		if (!read_line_old(f_in, line, sizeof line, 0))
			return -1;
	}

	if (!*line || strcmp(line, "#list") == 0) {
		rprintf(FLOG, "module-list request from %s (%s)\n",
			host, addr);
		send_listing(f_out);
		return -1;
	}

	if (*line == '#') {
		/* it's some sort of command that I don't understand */
		io_printf(f_out, "@ERROR: Unknown command '%s'\n", line);
		return -1;
	}

	if ((i = lp_number(line)) < 0) {
		rprintf(FLOG, "unknown module '%s' tried from %s (%s)\n",
			line, host, addr);
		io_printf(f_out, "@ERROR: Unknown module '%s'\n", line);
		return -1;
	}

#ifdef HAVE_SIGACTION
	sigact.sa_flags = SA_NOCLDSTOP;
#endif
	SIGACTION(SIGCHLD, remember_children);

	return rsync_module(f_in, f_out, i, addr, host);
}

static void create_pid_file(void)
{
	char *pid_file = lp_pid_file();
	char pidbuf[32];
	STRUCT_STAT st1, st2;
	char *fail = NULL;

	if (!pid_file || !*pid_file)
		return;

#ifdef O_NOFOLLOW
#define SAFE_OPEN_FLAGS (O_CREAT|O_NOFOLLOW)
#else
#define SAFE_OPEN_FLAGS (O_CREAT)
#endif

	/* These tests make sure that a temp-style lock dir is handled safely. */
	st1.st_mode = 0;
	if (do_lstat(pid_file, &st1) == 0 && !S_ISREG(st1.st_mode) && unlink(pid_file) < 0)
		fail = "unlink";
	else if ((pid_file_fd = do_open(pid_file, O_RDWR|SAFE_OPEN_FLAGS, 0664)) < 0)
		fail = S_ISREG(st1.st_mode) ? "open" : "create";
	else if (!lock_range(pid_file_fd, 0, 4))
		fail = "lock";
	else if (do_fstat(pid_file_fd, &st1) < 0)
		fail = "fstat opened";
	else if (st1.st_size > (int)sizeof pidbuf)
		fail = "find small";
	else if (do_lstat(pid_file, &st2) < 0)
		fail = "lstat";
	else if (!S_ISREG(st1.st_mode))
		fail = "avoid file overwrite race for";
	else if (st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino)
		fail = "verify stat info for";
#ifdef HAVE_FTRUNCATE
	else if (do_ftruncate(pid_file_fd, 0) < 0)
		fail = "truncate";
#endif
	else {
		pid_t pid = getpid();
		int len = snprintf(pidbuf, sizeof pidbuf, "%d\n", (int)pid);
#ifndef HAVE_FTRUNCATE
		/* What can we do with a too-long file and no truncate? I guess we'll add extra newlines. */
		while (len < st1.st_size) /* We already verified that st_size chars fits in the buffer. */
			pidbuf[len++] = '\n';
		/* We don't need the buffer to end in a '\0' (and we may not have room to add it). */
#endif
		if (write(pid_file_fd, pidbuf, len) != len)
			 fail = "write";
		cleanup_set_pid(pid); /* Mark the file for removal on exit, even if the write failed. */
	}

	if (fail) {
		char msg[1024];
		snprintf(msg, sizeof msg, "failed to %s pid file %s: %s\n",
			fail, pid_file, strerror(errno));
		fputs(msg, stderr);
		rprintf(FLOG, "%s", msg);
		exit_cleanup(RERR_FILEIO);
	}

	/* The file is left open so that the lock remains valid. It is closed in our forked child procs. */
}

/* Become a daemon, discarding the controlling terminal. */
static void become_daemon(void)
{
	int i;
	pid_t pid = fork();

	if (pid) {
		if (pid < 0) {
			fprintf(stderr, "failed to fork: %s\n", strerror(errno));
			exit_cleanup(RERR_FILEIO);
		}
		_exit(0);
	}

	create_pid_file();

	/* detach from the terminal */
#ifdef HAVE_SETSID
	setsid();
#elif defined TIOCNOTTY
	i = open("/dev/tty", O_RDWR);
	if (i >= 0) {
		ioctl(i, (int)TIOCNOTTY, (char *)0);
		close(i);
	}
#endif
	/* make sure that stdin, stdout an stderr don't stuff things
	 * up (library functions, for example) */
	for (i = 0; i < 3; i++) {
		close(i);
		open("/dev/null", O_RDWR);
	}
}

int daemon_main(void)
{
	if (is_a_socket(STDIN_FILENO)) {
		int i;

		/* we are running via inetd - close off stdout and
		 * stderr so that library functions (and getopt) don't
		 * try to use them. Redirect them to /dev/null */
		for (i = 1; i < 3; i++) {
			close(i);
			open("/dev/null", O_RDWR);
		}

		return start_daemon(STDIN_FILENO, STDIN_FILENO);
	}

	if (!load_config(1)) {
		fprintf(stderr, "Failed to parse config file: %s\n", config_file);
		exit_cleanup(RERR_SYNTAX);
	}
	set_dparams(0);

	if (no_detach)
		create_pid_file();
	else
		become_daemon();

	if (rsync_port == 0 && (rsync_port = lp_rsync_port()) == 0)
		rsync_port = RSYNC_PORT;
	if (bind_address == NULL && *lp_bind_address())
		bind_address = lp_bind_address();

	log_init(0);

	rprintf(FLOG, "rsyncd version %s starting, listening on port %d\n",
		rsync_version(), rsync_port);
	/* TODO: If listening on a particular address, then show that
	 * address too.  In fact, why not just do getnameinfo on the
	 * local address??? */

	start_accept_loop(rsync_port, start_daemon);
	return -1;
}
/*
 * Compatibility routines for older rsync protocol versions.
 *
 * Copyright (C) Andrew Tridgell 1996
 * Copyright (C) Paul Mackerras 1996
 * Copyright (C) 2004-2022 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"
#include "itypes.h"
#include "ifuncs.h"

extern int am_server;
extern int am_sender;
extern int local_server;
extern int inplace;
extern int recurse;
extern int use_qsort;
extern int allow_inc_recurse;
extern int preallocate_files;
extern int append_mode;
extern int fuzzy_basis;
extern int read_batch;
extern int write_batch;
extern int delay_updates;
extern int checksum_seed;
extern int basis_dir_cnt;
extern int prune_empty_dirs;
extern int protocol_version;
extern int protect_args;
extern int preserve_uid;
extern int preserve_gid;
extern int preserve_atimes;
extern int preserve_crtimes;
extern int preserve_acls;
extern int preserve_xattrs;
extern int xfer_flags_as_varint;
extern int need_messages_from_generator;
extern int delete_mode, delete_before, delete_during, delete_after;
extern int do_compression;
extern int do_compression_level;
extern int saw_stderr_opt;
extern int msgs2stderr;
extern char *shell_cmd;
extern char *partial_dir;
extern char *files_from;
extern char *filesfrom_host;
extern const char *checksum_choice;
extern const char *compress_choice;
extern char *daemon_auth_choices;
extern filter_rule_list filter_list;
extern int need_unsorted_flist;
#ifdef ICONV_OPTION
extern iconv_t ic_send, ic_recv;
extern char *iconv_opt;
#endif
extern struct name_num_obj valid_checksums, valid_auth_checksums;

extern struct name_num_item *xfer_sum_nni;

int remote_protocol = 0;
int file_extra_cnt = 0; /* count of file-list extras that everyone gets */
int inc_recurse = 0;
int compat_flags = 0;
int use_safe_inc_flist = 0;
int want_xattr_optim = 0;
int proper_seed_order = 0;
int inplace_partial = 0;
int do_negotiated_strings = 0;
int xmit_id0_names = 0;

struct name_num_item *xattr_sum_nni;
int xattr_sum_len = 0;

/* These index values are for the file-list's extra-attribute array. */
int pathname_ndx, depth_ndx, atimes_ndx, crtimes_ndx, uid_ndx, gid_ndx, acls_ndx, xattrs_ndx, unsort_ndx;

int receiver_symlink_times = 0; /* receiver can set the time on a symlink */
int sender_symlink_iconv = 0;	/* sender should convert symlink content */

#ifdef ICONV_OPTION
int filesfrom_convert = 0;
#endif

#define MAX_NSTR_STRLEN 256

struct name_num_item valid_compressions_items[] = {
#ifdef SUPPORT_ZSTD
	{ CPRES_ZSTD, 0, "zstd", NULL },
#endif
#ifdef SUPPORT_LZ4
	{ CPRES_LZ4, 0, "lz4", NULL },
#endif
	{ CPRES_ZLIBX, 0, "zlibx", NULL },
	{ CPRES_ZLIB, 0, "zlib", NULL },
	{ CPRES_NONE, 0, "none", NULL },
	{ 0, 0, NULL, NULL }
};

struct name_num_obj valid_compressions = {
	"compress", NULL, 0, 0, valid_compressions_items
};

#define CF_INC_RECURSE	 (1<<0)
#define CF_SYMLINK_TIMES (1<<1)
#define CF_SYMLINK_ICONV (1<<2)
#define CF_SAFE_FLIST	 (1<<3)
#define CF_AVOID_XATTR_OPTIM (1<<4)
#define CF_CHKSUM_SEED_FIX (1<<5)
#define CF_INPLACE_PARTIAL_DIR (1<<6)
#define CF_VARINT_FLIST_FLAGS (1<<7)
#define CF_ID0_NAMES (1<<8)

static const char *client_info;

/* The server makes sure that if either side only supports a pre-release
 * version of a protocol, that both sides must speak a compatible version
 * of that protocol for it to be advertised as available. */
static void check_sub_protocol(void)
{
	char *dot;
	int their_protocol, their_sub;
	int our_sub = get_subprotocol_version();

	/* client_info starts with VER.SUB string if client is a pre-release. */
	if (!(their_protocol = atoi(client_info))
	 || !(dot = strchr(client_info, '.'))
	 || !(their_sub = atoi(dot+1))) {
#if SUBPROTOCOL_VERSION != 0
		if (our_sub)
			protocol_version--;
#endif
		return;
	}

	if (their_protocol < protocol_version) {
		if (their_sub)
			protocol_version = their_protocol - 1;
		return;
	}

	if (their_protocol > protocol_version)
		their_sub = 0; /* 0 == final version of older protocol */
	if (their_sub != our_sub)
		protocol_version--;
}

void set_allow_inc_recurse(void)
{
	if (!local_server)
		client_info = shell_cmd ? shell_cmd : "";
	else if (am_server) {
		char buf[64];
		maybe_add_e_option(buf, sizeof buf);
		client_info = *buf ? strdup(buf+1) : ""; /* The +1 skips the leading "e". */
	}

	if (!recurse || use_qsort)
		allow_inc_recurse = 0;
	else if (!am_sender
	 && (delete_before || delete_after
	  || delay_updates || prune_empty_dirs))
		allow_inc_recurse = 0;
	else if (am_server && strchr(client_info, 'i') == NULL)
		allow_inc_recurse = 0;
}

void parse_compress_choice(int final_call)
{
	if (valid_compressions.negotiated_nni)
		do_compression = valid_compressions.negotiated_nni->num;
	else if (compress_choice) {
		struct name_num_item *nni = get_nni_by_name(&valid_compressions, compress_choice, -1);
		if (!nni) {
			rprintf(FERROR, "unknown compress name: %s\n", compress_choice);
			exit_cleanup(RERR_UNSUPPORTED);
		}
		do_compression = nni->num;
		if (am_server)
			validate_choice_vs_env(NSTR_COMPRESS, do_compression, -1);
	} else if (do_compression)
		do_compression = CPRES_ZLIB;
	else
		do_compression = CPRES_NONE;

	if (do_compression != CPRES_NONE && final_call)
		init_compression_level(); /* There's a chance this might turn compression off! */

	if (do_compression == CPRES_NONE)
		compress_choice = NULL;

	/* Snag the compression name for both write_batch's option output & the following debug output. */
	if (valid_compressions.negotiated_nni)
		compress_choice = valid_compressions.negotiated_nni->name;
	else if (compress_choice == NULL) {
		struct name_num_item *nni = get_nni_by_num(&valid_compressions, do_compression);
		compress_choice = nni ? nni->name : "UNKNOWN";
	}

	if (final_call && DEBUG_GTE(NSTR, am_server ? 3 : 1)
	 && (do_compression != CPRES_NONE || do_compression_level != CLVL_NOT_SPECIFIED)) {
		rprintf(FINFO, "%s%s compress: %s (level %d)\n",
			am_server ? "Server" : "Client",
			valid_compressions.negotiated_nni ? " negotiated" : "",
			compress_choice, do_compression_level);
	}
}

struct name_num_item *get_nni_by_name(struct name_num_obj *nno, const char *name, int len)
{
	struct name_num_item *nni;

	if (len < 0)
		len = strlen(name);

	for (nni = nno->list; nni->name; nni++) {
		if (nni->num == CSUM_gone)
			continue;
		if (strncasecmp(name, nni->name, len) == 0 && nni->name[len] == '\0')
			return nni;
	}

	return NULL;
}

struct name_num_item *get_nni_by_num(struct name_num_obj *nno, int num)
{
	struct name_num_item *nni;

	for (nni = nno->list; nni->name; nni++) {
		if (num == nni->num)
			return nni;
	}

	return NULL;
}

static void init_nno_saw(struct name_num_obj *nno, int val)
{
	struct name_num_item *nni;
	int cnt;

	if (!nno->saw_len) {
		for (nni = nno->list; nni->name; nni++) {
			if (nni->num >= nno->saw_len)
				nno->saw_len = nni->num + 1;
		}
	}

	if (!nno->saw) {
		nno->saw = new_array0(uchar, nno->saw_len);

		/* We'll take this opportunity to set the main_nni values for duplicates. */
		for (cnt = 1, nni = nno->list; nni->name; nni++, cnt++) {
			if (nni->num == CSUM_gone)
				continue;
			if (nno->saw[nni->num])
				nni->main_nni = &nno->list[nno->saw[nni->num]-1];
			else
				nno->saw[nni->num] = cnt;
		}
	}

	memset(nno->saw, val, nno->saw_len);
}

/* Simplify the user-provided string so that it contains valid names without any duplicates.
 * It also sets the "saw" flags to a 1-relative count of which name was seen first. */
static int parse_nni_str(struct name_num_obj *nno, const char *from, char *tobuf, int tobuf_len)
{
	char *to = tobuf, *tok = NULL;
	int saw_tok = 0, cnt = 0;

	while (1) {
		int at_space = isSpace(from);
		char ch = *from++;
		if (ch == '&')
			ch = '\0';
		if (!ch || at_space) {
			if (tok) {
				struct name_num_item *nni = get_nni_by_name(nno, tok, to - tok);
				if (nni && !nno->saw[nni->num]) {
					nno->saw[nni->num] = ++cnt;
					if (nni->main_nni) {
						to = tok + strlcpy(tok, nni->main_nni->name, tobuf_len - (tok - tobuf));
						if (to - tobuf >= tobuf_len) {
							to = tok - 1;
							break;
						}
					}
				} else
					to = tok - (tok != tobuf);
				saw_tok = 1;
				tok = NULL;
			}
			if (!ch)
				break;
			continue;
		}
		if (!tok) {
			if (to != tobuf)
				*to++ = ' ';
			tok = to;
		}
		if (to - tobuf >= tobuf_len - 1) {
			to = tok - (tok != tobuf);
			break;
		}
		*to++ = ch;
	}
	*to = '\0';

	if (saw_tok && to == tobuf)
		return strlcpy(tobuf, "INVALID", MAX_NSTR_STRLEN);

	return to - tobuf;
}

static int parse_negotiate_str(struct name_num_obj *nno, char *tmpbuf)
{
	struct name_num_item *nni, *ret = NULL;
	int best = nno->saw_len; /* We want best == 1 from the client list, so start with a big number. */
	char *space, *tok = tmpbuf;
	while (tok) {
		while (*tok == ' ') tok++; /* Should be unneeded... */
		if (!*tok)
			break;
		if ((space = strchr(tok, ' ')) != NULL)
			*space = '\0';
		nni = get_nni_by_name(nno, tok, -1);
		if (space) {
			*space = ' ';
			tok = space + 1;
		} else
			tok = NULL;
		if (!nni || !nno->saw[nni->num] || best <= nno->saw[nni->num])
			continue;
		ret = nni;
		best = nno->saw[nni->num];
		if (best == 1 || am_server) /* The server side stops at the first acceptable client choice */
			break;
	}
	if (ret) {
		free(nno->saw);
		nno->saw = NULL;
		nno->negotiated_nni = ret->main_nni ? ret->main_nni : ret;
		return 1;
	}
	return 0;
}

/* This routine is always called with a tmpbuf of MAX_NSTR_STRLEN length, but the
 * buffer may be pre-populated with a "len" length string to use OR a len of -1
 * to tell us to read a string from the fd. */
static void recv_negotiate_str(int f_in, struct name_num_obj *nno, char *tmpbuf, int len)
{
	if (len < 0)
		len = read_vstring(f_in, tmpbuf, MAX_NSTR_STRLEN);

	if (DEBUG_GTE(NSTR, am_server ? 3 : 2)) {
		if (am_server)
			rprintf(FINFO, "Client %s list (on server): %s\n", nno->type, tmpbuf);
		else
			rprintf(FINFO, "Server %s list (on client): %s\n", nno->type, tmpbuf);
	}

	if (len > 0 && parse_negotiate_str(nno, tmpbuf))
		return;

	if (!am_server || !do_negotiated_strings) {
		char *cp = tmpbuf;
		int j;
		rprintf(FERROR, "Failed to negotiate a %s choice.\n", nno->type);
		rprintf(FERROR, "%s list: %s\n", am_server ? "Client" : "Server", tmpbuf);
		/* Recreate our original list from the saw values. This can't overflow our huge
		 * buffer because we don't have enough valid entries to get anywhere close. */
		for (j = 1, *cp = '\0'; j <= nno->saw_len; j++) {
			struct name_num_item *nni;
			for (nni = nno->list; nni->name; nni++) {
				if (nno->saw[nni->num] == j) {
					*cp++ = ' ';
					cp += strlcpy(cp, nni->name, MAX_NSTR_STRLEN - (cp - tmpbuf));
					break;
				}
			}
		}
		if (!*tmpbuf)
			strlcpy(cp, " INVALID", MAX_NSTR_STRLEN);
		rprintf(FERROR, "%s list:%s\n", am_server ? "Server" : "Client", tmpbuf);
	}

	exit_cleanup(RERR_UNSUPPORTED);
}

static const char *getenv_nstr(int ntype)
{
	const char *env_str = getenv(ntype == NSTR_COMPRESS ? "RSYNC_COMPRESS_LIST" : "RSYNC_CHECKSUM_LIST");

	/* When writing a batch file, we always negotiate an old-style choice. */
	if (write_batch)
		env_str = ntype == NSTR_COMPRESS ? "zlib" : protocol_version >= 30 ? "md5" : "md4";

	if (am_server && env_str) {
		char *cp = strchr(env_str, '&');
		if (cp)
			env_str = cp + 1;
	}

	return env_str;
}

void validate_choice_vs_env(int ntype, int num1, int num2)
{
	struct name_num_obj *nno = ntype == NSTR_COMPRESS ? &valid_compressions : &valid_checksums;
	const char *list_str = getenv_nstr(ntype);
	char tmpbuf[MAX_NSTR_STRLEN];

	if (!list_str)
		return;

	while (isSpace(list_str)) list_str++;

	if (!*list_str)
		return;

	init_nno_saw(nno, 0);
	parse_nni_str(nno, list_str, tmpbuf, MAX_NSTR_STRLEN);

	if (ntype == NSTR_CHECKSUM) /* If "md4" is in the env list, all the old MD4 choices are OK too. */
		nno->saw[CSUM_MD4_ARCHAIC] = nno->saw[CSUM_MD4_BUSTED] = nno->saw[CSUM_MD4_OLD] = nno->saw[CSUM_MD4];

	if (!nno->saw[num1] || (num2 >= 0 && !nno->saw[num2])) {
		rprintf(FERROR, "Your --%s-choice value (%s) was refused by the server.\n",
			ntype == NSTR_COMPRESS ? "compress" : "checksum",
			ntype == NSTR_COMPRESS ? compress_choice : checksum_choice);
		exit_cleanup(RERR_UNSUPPORTED);
	}

	free(nno->saw);
	nno->saw = NULL;
}

/* The saw buffer is initialized and used to store ordinal values from 1 to N
 * for the order of the args in the array.  If dup_markup == '\0', duplicates
 * are removed otherwise the char is prefixed to the duplicate term and, if it
 * is an opening paren/bracket/brace, the matching closing char is suffixed.
 * "none" is removed on the client side unless dup_markup != '\0'. */
int get_default_nno_list(struct name_num_obj *nno, char *to_buf, int to_buf_len, char dup_markup)
{
	struct name_num_item *nni;
	int len = 0, cnt = 0;
	char delim = '\0', post_delim;

	switch (dup_markup) {
	case '(': post_delim = ')'; break;
	case '[': post_delim = ']'; break;
	case '{': post_delim = '}'; break;
	default: post_delim = '\0'; break;
	}

	init_nno_saw(nno, 0);

	for (nni = nno->list, len = 0; nni->name; nni++) {
		if (nni->num == CSUM_gone)
			continue;
		if (nni->main_nni) {
			if (!dup_markup || nni->main_nni->num == CSUM_gone)
				continue;
			delim = dup_markup;
		}
		if (nni->num == 0 && !am_server && !dup_markup)
			continue;
		if (len)
			to_buf[len++]= ' ';
		if (delim) {
			to_buf[len++]= delim;
			delim = post_delim;
		}
		len += strlcpy(to_buf+len, nni->name, to_buf_len - len);
		if (len >= to_buf_len - 3)
			exit_cleanup(RERR_UNSUPPORTED); /* IMPOSSIBLE... */
		if (delim) {
			to_buf[len++]= delim;
			delim = '\0';
		}
		nno->saw[nni->num] = ++cnt;
	}

	return len;
}

static void send_negotiate_str(int f_out, struct name_num_obj *nno, int ntype)
{
	char tmpbuf[MAX_NSTR_STRLEN];
	const char *list_str = getenv_nstr(ntype);
	int len;

	if (list_str && *list_str) {
		init_nno_saw(nno, 0);
		len = parse_nni_str(nno, list_str, tmpbuf, MAX_NSTR_STRLEN);
		list_str = tmpbuf;
	} else
		list_str = NULL;

	if (!list_str || !*list_str)
		len = get_default_nno_list(nno, tmpbuf, MAX_NSTR_STRLEN, '\0');

	if (DEBUG_GTE(NSTR, am_server ? 3 : 2)) {
		if (am_server)
			rprintf(FINFO, "Server %s list (on server): %s\n", nno->type, tmpbuf);
		else
			rprintf(FINFO, "Client %s list (on client): %s\n", nno->type, tmpbuf);
	}

	/* Each side sends their list of valid names to the other side and then both sides
	 * pick the first name in the client's list that is also in the server's list. */
	if (do_negotiated_strings)
		write_vstring(f_out, tmpbuf, len);
}

static void negotiate_the_strings(int f_in, int f_out)
{
	/* We send all the negotiation strings before we start to read them to help avoid a slow startup. */

	init_checksum_choices();

	if (!checksum_choice)
		send_negotiate_str(f_out, &valid_checksums, NSTR_CHECKSUM);

	if (do_compression && !compress_choice)
		send_negotiate_str(f_out, &valid_compressions, NSTR_COMPRESS);

	if (valid_checksums.saw) {
		char tmpbuf[MAX_NSTR_STRLEN];
		int len;
		if (do_negotiated_strings)
			len = -1;
		else
			len = strlcpy(tmpbuf, protocol_version >= 30 ? "md5" : "md4", MAX_NSTR_STRLEN);
		recv_negotiate_str(f_in, &valid_checksums, tmpbuf, len);
	}

	if (valid_compressions.saw) {
		char tmpbuf[MAX_NSTR_STRLEN];
		int len;
		if (do_negotiated_strings)
			len = -1;
		else
			len = strlcpy(tmpbuf, "zlib", MAX_NSTR_STRLEN);
		recv_negotiate_str(f_in, &valid_compressions, tmpbuf, len);
	}

	/* If the other side is too old to negotiate, the above steps just made sure that
	 * the env didn't disallow the old algorithm. Mark things as non-negotiated. */
	if (!do_negotiated_strings)
		valid_checksums.negotiated_nni = valid_compressions.negotiated_nni = NULL;
}

void setup_protocol(int f_out,int f_in)
{
	assert(file_extra_cnt == 0);
	assert(EXTRA64_CNT == 2 || EXTRA64_CNT == 1);

	/* All int64 values must be set first so that they are guaranteed to be
	 * aligned for direct int64-pointer memory access. */
	if (preserve_atimes)
		atimes_ndx = (file_extra_cnt += EXTRA64_CNT);
	if (preserve_crtimes)
		crtimes_ndx = (file_extra_cnt += EXTRA64_CNT);
	if (am_sender) /* This is most likely in the file_extras64 union as well. */
		pathname_ndx = (file_extra_cnt += PTR_EXTRA_CNT);
	else
		depth_ndx = ++file_extra_cnt;
	if (preserve_uid)
		uid_ndx = ++file_extra_cnt;
	if (preserve_gid)
		gid_ndx = ++file_extra_cnt;
	if (preserve_acls && !am_sender)
		acls_ndx = ++file_extra_cnt;
	if (preserve_xattrs)
		xattrs_ndx = ++file_extra_cnt;

	if (am_server)
		set_allow_inc_recurse();

	if (remote_protocol == 0) {
		if (am_server && !local_server)
			check_sub_protocol();
		if (!read_batch)
			write_int(f_out, protocol_version);
		remote_protocol = read_int(f_in);
		if (protocol_version > remote_protocol)
			protocol_version = remote_protocol;
	}
	if (read_batch && remote_protocol > protocol_version) {
		rprintf(FERROR, "The protocol version in the batch file is too new (%d > %d).\n",
			remote_protocol, protocol_version);
		exit_cleanup(RERR_PROTOCOL);
	}

	if (DEBUG_GTE(PROTO, 1)) {
		rprintf(FINFO, "(%s) Protocol versions: remote=%d, negotiated=%d\n",
			am_server? "Server" : "Client", remote_protocol, protocol_version);
	}
	if (remote_protocol < MIN_PROTOCOL_VERSION
	 || remote_protocol > MAX_PROTOCOL_VERSION) {
		rprintf(FERROR,"protocol version mismatch -- is your shell clean?\n");
		rprintf(FERROR,"(see the rsync manpage for an explanation)\n");
		exit_cleanup(RERR_PROTOCOL);
	}
	if (remote_protocol < OLD_PROTOCOL_VERSION) {
		rprintf(FINFO,"%s is very old version of rsync, upgrade recommended.\n",
			am_server? "Client" : "Server");
	}
	if (protocol_version < MIN_PROTOCOL_VERSION) {
		rprintf(FERROR, "--protocol must be at least %d on the %s.\n",
			MIN_PROTOCOL_VERSION, am_server? "Server" : "Client");
		exit_cleanup(RERR_PROTOCOL);
	}
	if (protocol_version > PROTOCOL_VERSION) {
		rprintf(FERROR, "--protocol must be no more than %d on the %s.\n",
			PROTOCOL_VERSION, am_server? "Server" : "Client");
		exit_cleanup(RERR_PROTOCOL);
	}
	if (read_batch)
		check_batch_flags();

	if (!saw_stderr_opt && protocol_version <= 28 && am_server)
		msgs2stderr = 0; /* The client side may not have stderr setup for us. */

#ifndef SUPPORT_PREALLOCATION
	if (preallocate_files && !am_sender) {
		rprintf(FERROR, "preallocation is not supported on this %s\n",
			am_server ? "Server" : "Client");
		exit_cleanup(RERR_SYNTAX);
	}
#endif

	if (protocol_version < 30) {
		if (append_mode == 1)
			append_mode = 2;
		if (preserve_acls && !local_server) {
			rprintf(FERROR,
				"--acls requires protocol 30 or higher"
				" (negotiated %d).\n",
				protocol_version);
			exit_cleanup(RERR_PROTOCOL);
		}
		if (preserve_xattrs && !local_server) {
			rprintf(FERROR,
				"--xattrs requires protocol 30 or higher"
				" (negotiated %d).\n",
				protocol_version);
			exit_cleanup(RERR_PROTOCOL);
		}
	}

	if (delete_mode && !(delete_before+delete_during+delete_after)) {
		if (protocol_version < 30)
			delete_before = 1;
		else
			delete_during = 1;
	}

	if (protocol_version < 29) {
		if (fuzzy_basis) {
			rprintf(FERROR,
				"--fuzzy requires protocol 29 or higher"
				" (negotiated %d).\n",
				protocol_version);
			exit_cleanup(RERR_PROTOCOL);
		}

		if (basis_dir_cnt && inplace) {
			rprintf(FERROR,
				"%s with --inplace requires protocol 29 or higher"
				" (negotiated %d).\n",
				alt_dest_opt(0), protocol_version);
			exit_cleanup(RERR_PROTOCOL);
		}

		if (basis_dir_cnt > 1) {
			rprintf(FERROR,
				"Using more than one %s option requires protocol"
				" 29 or higher (negotiated %d).\n",
				alt_dest_opt(0), protocol_version);
			exit_cleanup(RERR_PROTOCOL);
		}

		if (prune_empty_dirs) {
			rprintf(FERROR,
				"--prune-empty-dirs requires protocol 29 or higher"
				" (negotiated %d).\n",
				protocol_version);
			exit_cleanup(RERR_PROTOCOL);
		}
	} else if (protocol_version >= 30) {
		if (am_server) {
			compat_flags = allow_inc_recurse ? CF_INC_RECURSE : 0;
#ifdef CAN_SET_SYMLINK_TIMES
			compat_flags |= CF_SYMLINK_TIMES;
#endif
#ifdef ICONV_OPTION
			compat_flags |= CF_SYMLINK_ICONV;
#endif
			if (strchr(client_info, 'f') != NULL)
				compat_flags |= CF_SAFE_FLIST;
			if (strchr(client_info, 'x') != NULL)
				compat_flags |= CF_AVOID_XATTR_OPTIM;
			if (strchr(client_info, 'C') != NULL)
				compat_flags |= CF_CHKSUM_SEED_FIX;
			if (strchr(client_info, 'I') != NULL)
				compat_flags |= CF_INPLACE_PARTIAL_DIR;
			if (strchr(client_info, 'u') != NULL)
				compat_flags |= CF_ID0_NAMES;
			if (strchr(client_info, 'v') != NULL) {
				do_negotiated_strings = 1;
				compat_flags |= CF_VARINT_FLIST_FLAGS;
			}
			if (strchr(client_info, 'V') != NULL) { /* Support a pre-release 'V' that got superseded */
				if (!write_batch)
					compat_flags |= CF_VARINT_FLIST_FLAGS;
				write_byte(f_out, compat_flags);
			} else
				write_varint(f_out, compat_flags);
		} else { /* read_varint() is compatible with the older write_byte() when the 0x80 bit isn't on. */
			compat_flags = read_varint(f_in);
			if  (compat_flags & CF_VARINT_FLIST_FLAGS)
				do_negotiated_strings = 1;
		}
		/* The inc_recurse var MUST be set to 0 or 1. */
		inc_recurse = compat_flags & CF_INC_RECURSE ? 1 : 0;
		want_xattr_optim = protocol_version >= 31 && !(compat_flags & CF_AVOID_XATTR_OPTIM);
		proper_seed_order = compat_flags & CF_CHKSUM_SEED_FIX ? 1 : 0;
		xfer_flags_as_varint = compat_flags & CF_VARINT_FLIST_FLAGS ? 1 : 0;
		xmit_id0_names = compat_flags & CF_ID0_NAMES ? 1 : 0;
		if (!xfer_flags_as_varint && preserve_crtimes) {
			fprintf(stderr, "Both rsync versions must be at least 3.2.0 for --crtimes.\n");
			exit_cleanup(RERR_PROTOCOL);
		}
		if (am_sender) {
			receiver_symlink_times = am_server
			    ? strchr(client_info, 'L') != NULL
			    : !!(compat_flags & CF_SYMLINK_TIMES);
		}
#ifdef CAN_SET_SYMLINK_TIMES
		else
			receiver_symlink_times = 1;
#endif
#ifdef ICONV_OPTION
		sender_symlink_iconv = iconv_opt && (am_server
		    ? strchr(client_info, 's') != NULL
		    : !!(compat_flags & CF_SYMLINK_ICONV));
#endif
		if (inc_recurse && !allow_inc_recurse) {
			/* This should only be able to happen in a batch. */
			fprintf(stderr,
				"Incompatible options specified for inc-recursive %s.\n",
				read_batch ? "batch file" : "connection");
			exit_cleanup(RERR_SYNTAX);
		}
		use_safe_inc_flist = (compat_flags & CF_SAFE_FLIST) || protocol_version >= 31;
		need_messages_from_generator = 1;
		if (compat_flags & CF_INPLACE_PARTIAL_DIR)
			inplace_partial = 1;
#ifdef CAN_SET_SYMLINK_TIMES
	} else if (!am_sender) {
		receiver_symlink_times = 1;
#endif
	}

	if (read_batch)
		do_negotiated_strings = 0;

	if (need_unsorted_flist && (!am_sender || inc_recurse))
		unsort_ndx = ++file_extra_cnt;

	if (partial_dir && *partial_dir != '/' && (!am_server || local_server)) {
		int rflags = FILTRULE_NO_PREFIXES | FILTRULE_DIRECTORY;
		if (!am_sender || protocol_version >= 30)
			rflags |= FILTRULE_PERISHABLE;
		parse_filter_str(&filter_list, partial_dir, rule_template(rflags), 0);
	}


#ifdef ICONV_OPTION
	if (protect_args && files_from) {
		if (am_sender)
			filesfrom_convert = filesfrom_host && ic_send != (iconv_t)-1;
		else
			filesfrom_convert = !filesfrom_host && ic_recv != (iconv_t)-1;
	}
#endif

	negotiate_the_strings(f_in, f_out);

	if (am_server) {
		if (!checksum_seed)
			checksum_seed = time(NULL) ^ (getpid() << 6);
		write_int(f_out, checksum_seed);
	} else {
		checksum_seed = read_int(f_in);
	}

	parse_checksum_choice(1); /* Sets file_sum_nni & xfer_sum_nni */
	parse_compress_choice(1); /* Sets do_compression */

	/* TODO in the future allow this algorithm to be chosen somehow, but it can't get too
	 * long or the size starts to cause a problem in the xattr abbrev/non-abbrev code. */
	xattr_sum_nni = parse_csum_name(NULL, 0);
	xattr_sum_len = csum_len_for_type(xattr_sum_nni->num, 0);

	if (write_batch && !am_server)
		write_batch_shell_file();

	init_flist();
}

void output_daemon_greeting(int f_out, int am_client)
{
	char tmpbuf[MAX_NSTR_STRLEN];
	int our_sub = get_subprotocol_version();

	init_checksum_choices();

	get_default_nno_list(&valid_auth_checksums, tmpbuf, MAX_NSTR_STRLEN, '\0');

	io_printf(f_out, "@RSYNCD: %d.%d %s\n", protocol_version, our_sub, tmpbuf);

	if (am_client && DEBUG_GTE(NSTR, 2))
		rprintf(FINFO, "Client %s list (on client): %s\n", valid_auth_checksums.type, tmpbuf);
}

void negotiate_daemon_auth(int f_out, int am_client)
{
	char tmpbuf[MAX_NSTR_STRLEN];
	int save_am_server = am_server;
	int md4_is_old = 0;

	if (!am_client)
		am_server = 1;

	if (daemon_auth_choices)
		strlcpy(tmpbuf, daemon_auth_choices, MAX_NSTR_STRLEN);
	else {
		strlcpy(tmpbuf, protocol_version >= 30 ? "md5" : "md4", MAX_NSTR_STRLEN);
		md4_is_old = 1;
	}

	if (am_client) {
		recv_negotiate_str(-1, &valid_auth_checksums, tmpbuf, strlen(tmpbuf));
		if (DEBUG_GTE(NSTR, 1)) {
			rprintf(FINFO, "Client negotiated %s: %s\n", valid_auth_checksums.type,
				valid_auth_checksums.negotiated_nni->name);
		}
	} else {
		if (!parse_negotiate_str(&valid_auth_checksums, tmpbuf)) {
			get_default_nno_list(&valid_auth_checksums, tmpbuf, MAX_NSTR_STRLEN, '\0');
			io_printf(f_out, "@ERROR: your client does not support one of our daemon-auth checksums: %s\n",
				  tmpbuf);
			exit_cleanup(RERR_UNSUPPORTED);
		}
	}
	am_server = save_am_server;
	if (md4_is_old && valid_auth_checksums.negotiated_nni->num == CSUM_MD4) {
		valid_auth_checksums.negotiated_nni->num = CSUM_MD4_OLD;
		valid_auth_checksums.negotiated_nni->flags = 0;
	}
}

int get_subprotocol_version()
{
#if SUBPROTOCOL_VERSION != 0
	return protocol_version < PROTOCOL_VERSION ? 0 : SUBPROTOCOL_VERSION;
#else
	return 0;
#endif
}
/*
 * Support the max connections option.
 *
 * Copyright (C) 1998 Andrew Tridgell
 * Copyright (C) 2006-2020 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"

/* A simple routine to do connection counting.  This returns 1 on success
 * and 0 on failure, with errno also being set if the open() failed (errno
 * will be 0 if the lock request failed). */
int claim_connection(char *fname, int max_connections)
{
	int fd, i;

	if (max_connections == 0)
		return 1;

	if ((fd = open(fname, O_RDWR|O_CREAT, 0600)) < 0)
		return 0;

	/* Find a free spot. */
	for (i = 0; i < max_connections; i++) {
		if (lock_range(fd, i*4, 4))
			return 1;
	}

	close(fd);

	/* A lock failure needs to return an errno of 0. */
	errno = 0;
	return 0;
}
/*
 * Deletion routines used in rsync.
 *
 * Copyright (C) 1996-2000 Andrew Tridgell
 * Copyright (C) 1996 Paul Mackerras
 * Copyright (C) 2002 Martin Pool <mbp@samba.org>
 * Copyright (C) 2003-2024 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"

extern int am_root;
extern int make_backups;
extern int max_delete;
extern char *backup_dir;
extern char *backup_suffix;
extern int backup_suffix_len;
extern struct stats stats;

int ignore_perishable = 0;
int non_perishable_cnt = 0;
int skipped_deletes = 0;

static inline int is_backup_file(char *fn)
{
	int k = strlen(fn) - backup_suffix_len;
	return k > 0 && strcmp(fn+k, backup_suffix) == 0;
}

/* The directory is about to be deleted: if DEL_RECURSE is given, delete all
 * its contents, otherwise just checks for content.  Returns DR_SUCCESS or
 * DR_NOT_EMPTY.  Note that fname must point to a MAXPATHLEN buffer!  (The
 * buffer is used for recursion, but returned unchanged.)
 */
static enum delret delete_dir_contents(char *fname, uint16 flags)
{
	struct file_list *dirlist;
	enum delret ret;
	unsigned remainder;
	void *save_filters;
	int j, dlen;
	char *p;

	if (DEBUG_GTE(DEL, 3)) {
		rprintf(FINFO, "delete_dir_contents(%s) flags=%d\n",
			fname, flags);
	}

	dlen = strlen(fname);
	save_filters = push_local_filters(fname, dlen);

	non_perishable_cnt = 0;
	dirlist = get_dirlist(fname, dlen, 0);
	ret = non_perishable_cnt ? DR_NOT_EMPTY : DR_SUCCESS;

	if (!dirlist->used)
		goto done;

	if (!(flags & DEL_RECURSE)) {
		ret = DR_NOT_EMPTY;
		goto done;
	}

	p = fname + dlen;
	if (dlen != 1 || *fname != '/')
		*p++ = '/';
	remainder = MAXPATHLEN - (p - fname);

	/* We do our own recursion, so make delete_item() non-recursive. */
	flags = (flags & ~(DEL_RECURSE|DEL_MAKE_ROOM|DEL_NO_UID_WRITE))
	      | DEL_DIR_IS_EMPTY;

	for (j = dirlist->used; j--; ) {
		struct file_struct *fp = dirlist->files[j];

		if (fp->flags & FLAG_MOUNT_DIR && S_ISDIR(fp->mode)) {
			if (DEBUG_GTE(DEL, 1)) {
				rprintf(FINFO,
					"mount point, %s, pins parent directory\n",
					f_name(fp, NULL));
			}
			ret = DR_NOT_EMPTY;
			continue;
		}

		strlcpy(p, fp->basename, remainder);
		if (!(fp->mode & S_IWUSR) && !am_root && fp->flags & FLAG_OWNED_BY_US)
			do_chmod(fname, fp->mode | S_IWUSR);
		/* Save stack by recursing to ourself directly. */
		if (S_ISDIR(fp->mode)) {
			if (delete_dir_contents(fname, flags | DEL_RECURSE) != DR_SUCCESS)
				ret = DR_NOT_EMPTY;
		}
		if (delete_item(fname, fp->mode, flags) != DR_SUCCESS)
			ret = DR_NOT_EMPTY;
	}

	fname[dlen] = '\0';

  done:
	flist_free(dirlist);
	pop_local_filters(save_filters);

	if (ret == DR_NOT_EMPTY) {
		rprintf(FINFO, "cannot delete non-empty directory: %s\n",
			fname);
	}
	return ret;
}

/* Delete a file or directory.  If DEL_RECURSE is set in the flags, this will
 * delete recursively.
 *
 * Note that fbuf must point to a MAXPATHLEN buffer if the mode indicates it's
 * a directory! (The buffer is used for recursion, but returned unchanged.)
 */
enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
{
	enum delret ret;
	char *what;
	int ok;

	if (DEBUG_GTE(DEL, 2)) {
		rprintf(FINFO, "delete_item(%s) mode=%o flags=%d\n",
			fbuf, (int)mode, (int)flags);
	}

	if (flags & DEL_NO_UID_WRITE)
		do_chmod(fbuf, mode | S_IWUSR);

	if (S_ISDIR(mode) && !(flags & DEL_DIR_IS_EMPTY)) {
		/* This only happens on the first call to delete_item() since
		 * delete_dir_contents() always calls us w/DEL_DIR_IS_EMPTY. */
		ignore_perishable = 1;
		/* If DEL_RECURSE is not set, this just reports emptiness. */
		ret = delete_dir_contents(fbuf, flags);
		ignore_perishable = 0;
		if (ret == DR_NOT_EMPTY || ret == DR_AT_LIMIT)
			goto check_ret;
		/* OK: try to delete the directory. */
	}

	if (!(flags & DEL_MAKE_ROOM) && max_delete >= 0 && stats.deleted_files >= max_delete) {
		skipped_deletes++;
		return DR_AT_LIMIT;
	}

	if (S_ISDIR(mode)) {
		what = "rmdir";
		ok = do_rmdir(fbuf) == 0;
	} else {
		if (make_backups > 0 && !(flags & DEL_FOR_BACKUP) && (backup_dir || !is_backup_file(fbuf))) {
			what = "make_backup";
			ok = make_backup(fbuf, True);
			if (ok == 2) {
				what = "unlink";
				ok = robust_unlink(fbuf) == 0;
			}
		} else {
			what = "unlink";
			ok = robust_unlink(fbuf) == 0;
		}
	}

	if (ok) {
		if (!(flags & DEL_MAKE_ROOM)) {
			log_delete(fbuf, mode);
			stats.deleted_files++;
			if (S_ISREG(mode)) {
				/* Nothing more to count */
			} else if (S_ISDIR(mode))
				stats.deleted_dirs++;
#ifdef SUPPORT_LINKS
			else if (S_ISLNK(mode))
				stats.deleted_symlinks++;
#endif
			else if (IS_DEVICE(mode))
				stats.deleted_devices++;
			else
				stats.deleted_specials++;
		}
		ret = DR_SUCCESS;
	} else {
		if (S_ISDIR(mode) && errno == ENOTEMPTY) {
			rprintf(FINFO, "cannot delete non-empty directory: %s\n",
				fbuf);
			ret = DR_NOT_EMPTY;
		} else if (errno != ENOENT) {
			rsyserr(FERROR_XFER, errno, "delete_file: %s(%s) failed",
				what, fbuf);
			ret = DR_FAILURE;
		} else
			ret = DR_SUCCESS;
	}

  check_ret:
	if (ret != DR_SUCCESS && flags & DEL_MAKE_ROOM) {
		const char *desc;
		switch (flags & DEL_MAKE_ROOM) {
		case DEL_FOR_FILE: desc = "regular file"; break;
		case DEL_FOR_DIR: desc = "directory"; break;
		case DEL_FOR_SYMLINK: desc = "symlink"; break;
		case DEL_FOR_DEVICE: desc = "device file"; break;
		case DEL_FOR_SPECIAL: desc = "special file"; break;
		default: exit_cleanup(RERR_UNSUPPORTED); /* IMPOSSIBLE */
		}
		rprintf(FERROR_XFER, "could not make way for %s %s: %s\n",
			flags & DEL_FOR_BACKUP ? "backup" : "new",
			desc, fbuf);
	}
	return ret;
}

uint16 get_del_for_flag(uint16 mode)
{
	if (S_ISREG(mode))
		return DEL_FOR_FILE;
	if (S_ISDIR(mode))
		return DEL_FOR_DIR;
	if (S_ISLNK(mode))
		return DEL_FOR_SYMLINK;
	if (IS_DEVICE(mode))
		return DEL_FOR_DEVICE;
	if (IS_SPECIAL(mode))
		return DEL_FOR_SPECIAL;
	exit_cleanup(RERR_UNSUPPORTED); /* IMPOSSIBLE */
}
/*
 * The filter include/exclude routines.
 *
 * Copyright (C) 1996-2001 Andrew Tridgell <tridge@samba.org>
 * Copyright (C) 1996 Paul Mackerras
 * Copyright (C) 2002 Martin Pool
 * Copyright (C) 2003-2024 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"
#include "ifuncs.h"

extern int am_server;
extern int am_sender;
extern int am_generator;
extern int eol_nulls;
extern int io_error;
extern int xfer_dirs;
extern int recurse;
extern int local_server;
extern int prune_empty_dirs;
extern int ignore_perishable;
extern int relative_paths;
extern int delete_mode;
extern int delete_excluded;
extern int cvs_exclude;
extern int sanitize_paths;
extern int protocol_version;
extern int trust_sender_args;
extern int module_id;

extern char curr_dir[MAXPATHLEN];
extern unsigned int curr_dir_len;
extern unsigned int module_dirlen;

filter_rule_list filter_list = { .debug_type = "" };
filter_rule_list cvs_filter_list = { .debug_type = " [global CVS]" };
filter_rule_list daemon_filter_list = { .debug_type = " [daemon]" };
filter_rule_list implied_filter_list = { .debug_type = " [implied]" };

int saw_xattr_filter = 0;
int trust_sender_args = 0;
int trust_sender_filter = 0;

/* Need room enough for ":MODS " prefix plus some room to grow. */
#define MAX_RULE_PREFIX (16)

#define SLASH_WILD3_SUFFIX "/***"

/* The dirbuf is set by push_local_filters() to the current subdirectory
 * relative to curr_dir that is being processed.  The path always has a
 * trailing slash appended, and the variable dirbuf_len contains the length
 * of this path prefix.  The path is always absolute. */
static char dirbuf[MAXPATHLEN+1];
static unsigned int dirbuf_len = 0;
static int dirbuf_depth;

/* This is True when we're scanning parent dirs for per-dir merge-files. */
static BOOL parent_dirscan = False;

/* This array contains a list of all the currently active per-dir merge
 * files.  This makes it easier to save the appropriate values when we
 * "push" down into each subdirectory. */
static filter_rule **mergelist_parents;
static int mergelist_cnt = 0;
static int mergelist_size = 0;

#define LOCAL_RULE   1
#define REMOTE_RULE  2
static uchar cur_elide_value = REMOTE_RULE;

/* Each filter_list_struct describes a singly-linked list by keeping track
 * of both the head and tail pointers.  The list is slightly unusual in that
 * a parent-dir's content can be appended to the end of the local list in a
 * special way:  the last item in the local list has its "next" pointer set
 * to point to the inherited list, but the local list's tail pointer points
 * at the end of the local list.  Thus, if the local list is empty, the head
 * will be pointing at the inherited content but the tail will be NULL.  To
 * help you visualize this, here are the possible list arrangements:
 *
 * Completely Empty                     Local Content Only
 * ==================================   ====================================
 * head -> NULL                         head -> Local1 -> Local2 -> NULL
 * tail -> NULL                         tail -------------^
 *
 * Inherited Content Only               Both Local and Inherited Content
 * ==================================   ====================================
 * head -> Parent1 -> Parent2 -> NULL   head -> L1 -> L2 -> P1 -> P2 -> NULL
 * tail -> NULL                         tail ---------^
 *
 * This means that anyone wanting to traverse the whole list to use it just
 * needs to start at the head and use the "next" pointers until it goes
 * NULL.  To add new local content, we insert the item after the tail item
 * and update the tail (obviously, if "tail" was NULL, we insert it at the
 * head).  To clear the local list, WE MUST NOT FREE THE INHERITED CONTENT
 * because it is shared between the current list and our parent list(s).
 * The easiest way to handle this is to simply truncate the list after the
 * tail item and then free the local list from the head.  When inheriting
 * the list for a new local dir, we just save off the filter_list_struct
 * values (so we can pop back to them later) and set the tail to NULL.
 */

static void teardown_mergelist(filter_rule *ex)
{
	int j;

	if (!ex->u.mergelist)
		return;

	if (DEBUG_GTE(FILTER, 2)) {
		rprintf(FINFO, "[%s] deactivating mergelist #%d%s\n",
			who_am_i(), mergelist_cnt - 1,
			ex->u.mergelist->debug_type);
	}

	free(ex->u.mergelist->debug_type);
	free(ex->u.mergelist);

	for (j = 0; j < mergelist_cnt; j++) {
		if (mergelist_parents[j] == ex) {
			mergelist_parents[j] = NULL;
			break;
		}
	}
	while (mergelist_cnt && mergelist_parents[mergelist_cnt-1] == NULL)
		mergelist_cnt--;
}

static void free_filter(filter_rule *ex)
{
	if (ex->rflags & FILTRULE_PERDIR_MERGE)
		teardown_mergelist(ex);
	free(ex->pattern);
	free(ex);
}

static void free_filters(filter_rule *ent)
{
	while (ent) {
		filter_rule *next = ent->next;
		free_filter(ent);
		ent = next;
	}
}

/* Build a filter structure given a filter pattern.  The value in "pat"
 * is not null-terminated.  "rule" is either held or freed, so the
 * caller should not free it. */
static void add_rule(filter_rule_list *listp, const char *pat, unsigned int pat_len,
		     filter_rule *rule, int xflags)
{
	const char *cp;
	unsigned int pre_len, suf_len, slash_cnt = 0;
	char *mention_rule_suffix;

	if (DEBUG_GTE(FILTER, 1) && pat_len && (pat[pat_len-1] == ' ' || pat[pat_len-1] == '\t'))
		mention_rule_suffix = " -- CAUTION: trailing whitespace!";
	else
		mention_rule_suffix = DEBUG_GTE(FILTER, 2) ? "" : NULL;
	if (mention_rule_suffix) {
		rprintf(FINFO, "[%s] add_rule(%s%.*s%s)%s%s\n",
			who_am_i(), get_rule_prefix(rule, pat, 0, NULL),
			(int)pat_len, pat, (rule->rflags & FILTRULE_DIRECTORY) ? "/" : "",
			listp->debug_type, mention_rule_suffix);
	}

	/* These flags also indicate that we're reading a list that
	 * needs to be filtered now, not post-filtered later. */
	if (xflags & (XFLG_ANCHORED2ABS|XFLG_ABS_IF_SLASH)
		&& (rule->rflags & FILTRULES_SIDES)
			== (am_sender ? FILTRULE_RECEIVER_SIDE : FILTRULE_SENDER_SIDE)) {
		/* This filter applies only to the other side.  Drop it. */
		free_filter(rule);
		return;
	}

	if (pat_len > 1 && pat[pat_len-1] == '/') {
		pat_len--;
		rule->rflags |= FILTRULE_DIRECTORY;
	}

	for (cp = pat; cp < pat + pat_len; cp++) {
		if (*cp == '/')
			slash_cnt++;
	}

	if (!(rule->rflags & (FILTRULE_ABS_PATH | FILTRULE_MERGE_FILE))
	 && ((xflags & (XFLG_ANCHORED2ABS|XFLG_ABS_IF_SLASH) && *pat == '/')
	  || (xflags & XFLG_ABS_IF_SLASH && slash_cnt))) {
		rule->rflags |= FILTRULE_ABS_PATH;
		if (*pat == '/')
			pre_len = dirbuf_len - module_dirlen - 1;
		else
			pre_len = 0;
	} else
		pre_len = 0;

	/* The daemon wants dir-exclude rules to get an appended "/" + "***". */
	if (xflags & XFLG_DIR2WILD3
	 && BITS_SETnUNSET(rule->rflags, FILTRULE_DIRECTORY, FILTRULE_INCLUDE)) {
		rule->rflags &= ~FILTRULE_DIRECTORY;
		suf_len = sizeof SLASH_WILD3_SUFFIX - 1;
	} else
		suf_len = 0;

	rule->pattern = new_array(char, pre_len + pat_len + suf_len + 1);
	if (pre_len) {
		memcpy(rule->pattern, dirbuf + module_dirlen, pre_len);
		for (cp = rule->pattern; cp < rule->pattern + pre_len; cp++) {
			if (*cp == '/')
				slash_cnt++;
		}
	}
	rule->elide = 0;
	strlcpy(rule->pattern + pre_len, pat, pat_len + 1);
	pat_len += pre_len;
	if (suf_len) {
		memcpy(rule->pattern + pat_len, SLASH_WILD3_SUFFIX, suf_len+1);
		pat_len += suf_len;
		slash_cnt++;
	}

	if (strpbrk(rule->pattern, "*[?")) {
		rule->rflags |= FILTRULE_WILD;
		if ((cp = strstr(rule->pattern, "**")) != NULL) {
			rule->rflags |= FILTRULE_WILD2;
			/* If the pattern starts with **, note that. */
			if (cp == rule->pattern)
				rule->rflags |= FILTRULE_WILD2_PREFIX;
			/* If the pattern ends with ***, note that. */
			if (pat_len >= 3
			 && rule->pattern[pat_len-3] == '*'
			 && rule->pattern[pat_len-2] == '*'
			 && rule->pattern[pat_len-1] == '*')
				rule->rflags |= FILTRULE_WILD3_SUFFIX;
		}
	}

	if (rule->rflags & FILTRULE_PERDIR_MERGE) {
		filter_rule_list *lp;
		unsigned int len;
		int i;

		if ((cp = strrchr(rule->pattern, '/')) != NULL)
			cp++;
		else
			cp = rule->pattern;

		/* If the local merge file was already mentioned, don't
		 * add it again. */
		for (i = 0; i < mergelist_cnt; i++) {
			filter_rule *ex = mergelist_parents[i];
			const char *s;
			if (!ex)
				continue;
			s = strrchr(ex->pattern, '/');
			if (s)
				s++;
			else
				s = ex->pattern;
			len = strlen(s);
			if (len == pat_len - (cp - rule->pattern) && memcmp(s, cp, len) == 0) {
				free_filter(rule);
				return;
			}
		}

		lp = new_array0(filter_rule_list, 1);
		if (asprintf(&lp->debug_type, " [per-dir %s]", cp) < 0)
			out_of_memory("add_rule");
		rule->u.mergelist = lp;

		if (mergelist_cnt == mergelist_size) {
			mergelist_size += 5;
			mergelist_parents = realloc_array(mergelist_parents, filter_rule *, mergelist_size);
		}
		if (DEBUG_GTE(FILTER, 2)) {
			rprintf(FINFO, "[%s] activating mergelist #%d%s\n",
				who_am_i(), mergelist_cnt, lp->debug_type);
		}
		mergelist_parents[mergelist_cnt++] = rule;
	} else
		rule->u.slash_cnt = slash_cnt;

	if (!listp->tail) {
		rule->next = listp->head;
		listp->head = listp->tail = rule;
	} else {
		rule->next = listp->tail->next;
		listp->tail->next = rule;
		listp->tail = rule;
	}
}

/* If the wildcards failed, the remote shell might give us a file matching the literal
 * wildcards.  Since "*" & "?" already match themselves, this just needs to deal with
 * failed "[foo]" idioms.
 */
static void maybe_add_literal_brackets_rule(filter_rule const *based_on, int arg_len)
{
	filter_rule *rule;
	const char *arg = based_on->pattern, *cp;
	char *p;
	int cnt = 0;

	if (arg_len < 0)
		arg_len = strlen(arg);

	for (cp = arg; *cp; cp++) {
		if (*cp == '\\' && cp[1]) {
			cp++;
		} else if (*cp == '[')
			cnt++;
	}
	if (!cnt)
		return;

	rule = new0(filter_rule);
	rule->rflags = based_on->rflags;
	rule->u.slash_cnt = based_on->u.slash_cnt;
	p = rule->pattern = new_array(char, arg_len + cnt + 1);
	for (cp = arg; *cp; ) {
		if (*cp == '\\' && cp[1]) {
			*p++ = *cp++;
		} else if (*cp == '[')
			*p++ = '\\';
		*p++ = *cp++;
	}
	*p++ = '\0';

	rule->next = implied_filter_list.head;
	implied_filter_list.head = rule;
	if (DEBUG_GTE(FILTER, 3)) {
		rprintf(FINFO, "[%s] add_implied_include(%s%s)\n", who_am_i(), rule->pattern,
			rule->rflags & FILTRULE_DIRECTORY ? "/" : "");
	}
}

static char *partial_string_buf = NULL;
static int partial_string_len = 0;
void implied_include_partial_string(const char *s_start, const char *s_end)
{
	partial_string_len = s_end - s_start;
	if (partial_string_len <= 0 || partial_string_len >= MAXPATHLEN) { /* too-large should be impossible... */
		partial_string_len = 0;
		return;
	}
	if (!partial_string_buf)
		partial_string_buf = new_array(char, MAXPATHLEN);
	memcpy(partial_string_buf, s_start, partial_string_len);
}

void free_implied_include_partial_string(void)
{
	if (partial_string_buf) {
		if (partial_string_len)
			add_implied_include("", 0);
		free(partial_string_buf);
		partial_string_buf = NULL;
	}
	partial_string_len = 0; /* paranoia */
}

/* Each arg the client sends to the remote sender turns into an implied include
 * that the receiver uses to validate the file list from the sender. */
void add_implied_include(const char *arg, int skip_daemon_module)
{
	int arg_len, saw_wild = 0, saw_live_open_brkt = 0, backslash_cnt = 0;
	int slash_cnt = 0;
	const char *cp;
	char *p;
	if (trust_sender_args)
		return;
	if (partial_string_len) {
		arg_len = strlen(arg);
		if (partial_string_len + arg_len >= MAXPATHLEN) {
			partial_string_len = 0;
			return; /* Should be impossible... */
		}
		memcpy(partial_string_buf + partial_string_len, arg, arg_len + 1);
		partial_string_len = 0;
		arg = partial_string_buf;
	}
	if (skip_daemon_module) {
		if ((cp = strchr(arg, '/')) != NULL)
			arg = cp + 1;
		else
			arg = "";
	}
	if (relative_paths) {
		if ((cp = strstr(arg, "/./")) != NULL)
			arg = cp + 3;
	} else if ((cp = strrchr(arg, '/')) != NULL) {
		arg = cp + 1;
	}
	if (*arg == '.' && arg[1] == '\0')
		arg++;
	arg_len = strlen(arg);
	if (arg_len) {
		char *new_pat;
		if (strpbrk(arg, "*[?")) {
			/* We need to add room to escape backslashes if wildcard chars are present. */
			for (cp = arg; (cp = strchr(cp, '\\')) != NULL; cp++)
				arg_len++;
			saw_wild = 1;
		}
		arg_len++; /* Leave room for the prefixed slash */
		p = new_pat = new_array(char, arg_len + 1);
		*p++ = '/';
		slash_cnt++;
		for (cp = arg; *cp; ) {
			switch (*cp) {
			  case '\\':
				if (cp[1] == ']') {
					if (!saw_wild)
						cp++; /* A \] in a non-wild filter causes a problem, so drop the \ . */
				} else if (!strchr("*[?", cp[1])) {
					backslash_cnt++;
					if (saw_wild)
						*p++ = '\\';
				}
				*p++ = *cp++;
				break;
			  case '/':
				if (p[-1] == '/') { /* This is safe because of the initial slash. */
					if (*++cp == '\0') {
						slash_cnt--;
						p--;
					}
				} else if (cp[1] == '\0') {
					cp++;
				} else {
					slash_cnt++;
					*p++ = *cp++;
				}
				break;
			  case '.':
				if (p[-1] == '/') {
					if (cp[1] == '/') {
						cp += 2;
						if (!*cp) {
							slash_cnt--;
							p--;
						}
					} else if (cp[1] == '\0') {
						cp++;
						slash_cnt--;
						p--;
					} else
						*p++ = *cp++;
				} else
					*p++ = *cp++;
				break;
			  case '[':
				saw_live_open_brkt = 1;
				*p++ = *cp++;
				break;
			  default:
				*p++ = *cp++;
				break;
			}
		}
		*p = '\0';
		arg_len = p - new_pat;
		if (!arg_len)
			free(new_pat);
		else {
			filter_rule *rule = new0(filter_rule);
			rule->rflags = FILTRULE_INCLUDE + (saw_wild ? FILTRULE_WILD : 0);
			rule->u.slash_cnt = slash_cnt;
			arg = rule->pattern = new_pat;
			if (!implied_filter_list.head)
				implied_filter_list.head = implied_filter_list.tail = rule;
			else {
				rule->next = implied_filter_list.head;
				implied_filter_list.head = rule;
			}
			if (DEBUG_GTE(FILTER, 3))
				rprintf(FINFO, "[%s] add_implied_include(%s)\n", who_am_i(), arg);
			if (saw_live_open_brkt)
				maybe_add_literal_brackets_rule(rule, arg_len);
			if (relative_paths && slash_cnt) {
				int sub_slash_cnt = slash_cnt;
				while ((p = strrchr(new_pat, '/')) != NULL && p != new_pat) {
					filter_rule const *ent;
					filter_rule *R_rule;
					int found = 0;
					*p = '\0';
					for (ent = implied_filter_list.head; ent; ent = ent->next) {
						if (ent != rule && strcmp(ent->pattern, new_pat) == 0) {
							found = 1;
							break;
						}
					}
					if (found) {
						*p = '/';
						break; /* We added all parent dirs already */
					}
					R_rule = new0(filter_rule);
					R_rule->rflags = FILTRULE_INCLUDE | FILTRULE_DIRECTORY;
					/* Check if our sub-path has wildcards or escaped backslashes */
					if (saw_wild && strpbrk(new_pat, "*[?\\"))
						R_rule->rflags |= FILTRULE_WILD;
					R_rule->pattern = strdup(new_pat);
					R_rule->u.slash_cnt = --sub_slash_cnt;
					R_rule->next = implied_filter_list.head;
					implied_filter_list.head = R_rule;
					if (DEBUG_GTE(FILTER, 3)) {
						rprintf(FINFO, "[%s] add_implied_include(%s/)\n",
							who_am_i(), R_rule->pattern);
					}
					if (saw_live_open_brkt)
						maybe_add_literal_brackets_rule(R_rule, -1);
				}
				for (p = new_pat; sub_slash_cnt < slash_cnt; sub_slash_cnt++) {
					p += strlen(p);
					*p = '/';
				}
			}
		}
	}

	if (recurse || xfer_dirs) {
		/* Now create a rule with an added "/" & "**" or "*" at the end */
		filter_rule *rule = new0(filter_rule);
		rule->rflags = FILTRULE_INCLUDE | FILTRULE_WILD;
		if (recurse)
			rule->rflags |= FILTRULE_WILD2;
		/* We must leave enough room for / * * \0. */
		if (!saw_wild && backslash_cnt) {
			/* We are appending a wildcard, so now the backslashes need to be escaped. */
			p = rule->pattern = new_array(char, arg_len + backslash_cnt + 3 + 1);
			for (cp = arg; *cp; ) { /* Note that arg_len != 0 because backslash_cnt > 0 */
				if (*cp == '\\')
					*p++ = '\\';
				*p++ = *cp++;
			}
		} else {
			p = rule->pattern = new_array(char, arg_len + 3 + 1);
			if (arg_len) {
				memcpy(p, arg, arg_len);
				p += arg_len;
			}
		}
		*p++ = '/';
		*p++ = '*';
		if (recurse)
			*p++ = '*';
		*p = '\0';
		rule->u.slash_cnt = slash_cnt + 1;
		rule->next = implied_filter_list.head;
		implied_filter_list.head = rule;
		if (DEBUG_GTE(FILTER, 3))
			rprintf(FINFO, "[%s] add_implied_include(%s)\n", who_am_i(), rule->pattern);
		if (saw_live_open_brkt)
			maybe_add_literal_brackets_rule(rule, p - rule->pattern);
	}
}

/* This frees any non-inherited items, leaving just inherited items on the list. */
static void pop_filter_list(filter_rule_list *listp)
{
	filter_rule *inherited;

	if (!listp->tail)
		return;

	inherited = listp->tail->next;

	/* Truncate any inherited items from the local list. */
	listp->tail->next = NULL;
	/* Now free everything that is left. */
	free_filters(listp->head);

	listp->head = inherited;
	listp->tail = NULL;
}

/* This returns an expanded (absolute) filename for the merge-file name if
 * the name has any slashes in it OR if the parent_dirscan var is True;
 * otherwise it returns the original merge_file name.  If the len_ptr value
 * is non-NULL the merge_file name is limited by the referenced length
 * value and will be updated with the length of the resulting name.  We
 * always return a name that is null terminated, even if the merge_file
 * name was not. */
static char *parse_merge_name(const char *merge_file, unsigned int *len_ptr,
			      unsigned int prefix_skip)
{
	static char buf[MAXPATHLEN];
	char *fn, tmpbuf[MAXPATHLEN];
	unsigned int fn_len;

	if (!parent_dirscan && *merge_file != '/') {
		/* Return the name unchanged it doesn't have any slashes. */
		if (len_ptr) {
			const char *p = merge_file + *len_ptr;
			while (--p > merge_file && *p != '/') {}
			if (p == merge_file) {
				strlcpy(buf, merge_file, *len_ptr + 1);
				return buf;
			}
		} else if (strchr(merge_file, '/') == NULL)
			return (char *)merge_file;
	}

	fn = *merge_file == '/' ? buf : tmpbuf;
	if (sanitize_paths) {
		const char *r = prefix_skip ? "/" : NULL;
		/* null-terminate the name if it isn't already */
		if (len_ptr && merge_file[*len_ptr]) {
			char *to = fn == buf ? tmpbuf : buf;
			strlcpy(to, merge_file, *len_ptr + 1);
			merge_file = to;
		}
		if (!sanitize_path(fn, merge_file, r, dirbuf_depth, SP_DEFAULT)) {
			rprintf(FERROR, "merge-file name overflows: %s\n",
				merge_file);
			return NULL;
		}
		fn_len = strlen(fn);
	} else {
		strlcpy(fn, merge_file, len_ptr ? *len_ptr + 1 : MAXPATHLEN);
		fn_len = clean_fname(fn, CFN_COLLAPSE_DOT_DOT_DIRS);
	}

	/* If the name isn't in buf yet, it wasn't absolute. */
	if (fn != buf) {
		int d_len = dirbuf_len - prefix_skip;
		if (d_len + fn_len >= MAXPATHLEN) {
			rprintf(FERROR, "merge-file name overflows: %s\n", fn);
			return NULL;
		}
		memcpy(buf, dirbuf + prefix_skip, d_len);
		memcpy(buf + d_len, fn, fn_len + 1);
		fn_len = clean_fname(buf, CFN_COLLAPSE_DOT_DOT_DIRS);
	}

	if (len_ptr)
		*len_ptr = fn_len;
	return buf;
}

/* Sets the dirbuf and dirbuf_len values. */
void set_filter_dir(const char *dir, unsigned int dirlen)
{
	unsigned int len;
	if (*dir != '/') {
		memcpy(dirbuf, curr_dir, curr_dir_len);
		dirbuf[curr_dir_len] = '/';
		len = curr_dir_len + 1;
		if (len + dirlen >= MAXPATHLEN)
			dirlen = 0;
	} else
		len = 0;
	memcpy(dirbuf + len, dir, dirlen);
	dirbuf[dirlen + len] = '\0';
	dirbuf_len = clean_fname(dirbuf, CFN_COLLAPSE_DOT_DOT_DIRS);
	if (dirbuf_len > 1 && dirbuf[dirbuf_len-1] == '.'
	    && dirbuf[dirbuf_len-2] == '/')
		dirbuf_len -= 2;
	if (dirbuf_len != 1)
		dirbuf[dirbuf_len++] = '/';
	dirbuf[dirbuf_len] = '\0';
	if (sanitize_paths)
		dirbuf_depth = count_dir_elements(dirbuf + module_dirlen);
}

/* This routine takes a per-dir merge-file entry and finishes its setup.
 * If the name has a path portion then we check to see if it refers to a
 * parent directory of the first transfer dir.  If it does, we scan all the
 * dirs from that point through the parent dir of the transfer dir looking
 * for the per-dir merge-file in each one. */
static BOOL setup_merge_file(int mergelist_num, filter_rule *ex,
			     filter_rule_list *lp)
{
	char buf[MAXPATHLEN];
	char *x, *y, *pat = ex->pattern;
	unsigned int len;

	if (!(x = parse_merge_name(pat, NULL, 0)) || *x != '/')
		return 0;

	if (DEBUG_GTE(FILTER, 2)) {
		rprintf(FINFO, "[%s] performing parent_dirscan for mergelist #%d%s\n",
			who_am_i(), mergelist_num, lp->debug_type);
	}
	y = strrchr(x, '/');
	*y = '\0';
	ex->pattern = strdup(y+1);
	if (!*x)
		x = "/";
	if (*x == '/')
		strlcpy(buf, x, MAXPATHLEN);
	else
		pathjoin(buf, MAXPATHLEN, dirbuf, x);

	len = clean_fname(buf, CFN_COLLAPSE_DOT_DOT_DIRS);
	if (len != 1 && len < MAXPATHLEN-1) {
		buf[len++] = '/';
		buf[len] = '\0';
	}
	/* This ensures that the specified dir is a parent of the transfer. */
	for (x = buf, y = dirbuf; *x && *x == *y; x++, y++) {}
	if (*x)
		y += strlen(y); /* nope -- skip the scan */

	parent_dirscan = True;
	while (*y) {
		char save[MAXPATHLEN];
		/* copylen is strlen(y) which is < MAXPATHLEN. +1 for \0 */
		size_t copylen = strlcpy(save, y, MAXPATHLEN) + 1;
		*y = '\0';
		dirbuf_len = y - dirbuf;
		strlcpy(x, ex->pattern, MAXPATHLEN - (x - buf));
		parse_filter_file(lp, buf, ex, XFLG_ANCHORED2ABS);
		if (ex->rflags & FILTRULE_NO_INHERIT) {
			/* Free the undesired rules to clean up any per-dir
			 * mergelists they defined.  Otherwise pop_local_filters
			 * may crash trying to restore nonexistent state for
			 * those mergelists. */
			free_filters(lp->head);
			lp->head = NULL;
		}
		lp->tail = NULL;
		strlcpy(y, save, copylen);
		while ((*x++ = *y++) != '/') {}
	}
	parent_dirscan = False;
	if (DEBUG_GTE(FILTER, 2)) {
		rprintf(FINFO, "[%s] completed parent_dirscan for mergelist #%d%s\n",
			who_am_i(), mergelist_num, lp->debug_type);
	}
	free(pat);
	return 1;
}

struct local_filter_state {
	int mergelist_cnt;
	filter_rule_list mergelists[1];
};

/* Each time rsync changes to a new directory it call this function to
 * handle all the per-dir merge-files.  The "dir" value is the current path
 * relative to curr_dir (which might not be null-terminated).  We copy it
 * into dirbuf so that we can easily append a file name on the end. */
void *push_local_filters(const char *dir, unsigned int dirlen)
{
	struct local_filter_state *push;
	int i;

	set_filter_dir(dir, dirlen);
	if (DEBUG_GTE(FILTER, 2)) {
		rprintf(FINFO, "[%s] pushing local filters for %s\n",
			who_am_i(), dirbuf);
	}

	if (!mergelist_cnt) {
		/* No old state to save and no new merge files to push. */
		return NULL;
	}

	push = (struct local_filter_state *)new_array(char,
			  sizeof (struct local_filter_state)
			+ (mergelist_cnt-1) * sizeof (filter_rule_list));

	push->mergelist_cnt = mergelist_cnt;
	for (i = 0; i < mergelist_cnt; i++) {
		filter_rule *ex = mergelist_parents[i];
		if (!ex)
			continue;
		memcpy(&push->mergelists[i], ex->u.mergelist, sizeof (filter_rule_list));
	}

	/* Note: parse_filter_file() might increase mergelist_cnt, so keep
	 * this loop separate from the above loop. */
	for (i = 0; i < mergelist_cnt; i++) {
		filter_rule *ex = mergelist_parents[i];
		filter_rule_list *lp;
		if (!ex)
			continue;
		lp = ex->u.mergelist;

		if (DEBUG_GTE(FILTER, 2)) {
			rprintf(FINFO, "[%s] pushing mergelist #%d%s\n",
				who_am_i(), i, lp->debug_type);
		}

		lp->tail = NULL; /* Switch any local rules to inherited. */
		if (ex->rflags & FILTRULE_NO_INHERIT)
			lp->head = NULL;

		if (ex->rflags & FILTRULE_FINISH_SETUP) {
			ex->rflags &= ~FILTRULE_FINISH_SETUP;
			if (setup_merge_file(i, ex, lp))
				set_filter_dir(dir, dirlen);
		}

		if (strlcpy(dirbuf + dirbuf_len, ex->pattern,
		    MAXPATHLEN - dirbuf_len) < MAXPATHLEN - dirbuf_len) {
			parse_filter_file(lp, dirbuf, ex,
					  XFLG_ANCHORED2ABS);
		} else {
			io_error |= IOERR_GENERAL;
			rprintf(FERROR,
			    "cannot add local filter rules in long-named directory: %s\n",
			    full_fname(dirbuf));
		}
		dirbuf[dirbuf_len] = '\0';
	}

	return (void*)push;
}

void pop_local_filters(void *mem)
{
	struct local_filter_state *pop = (struct local_filter_state *)mem;
	int i;
	int old_mergelist_cnt = pop ? pop->mergelist_cnt : 0;

	if (DEBUG_GTE(FILTER, 2))
		rprintf(FINFO, "[%s] popping local filters\n", who_am_i());

	for (i = mergelist_cnt; i-- > 0; ) {
		filter_rule *ex = mergelist_parents[i];
		filter_rule_list *lp;
		if (!ex)
			continue;
		lp = ex->u.mergelist;

		if (DEBUG_GTE(FILTER, 2)) {
			rprintf(FINFO, "[%s] popping mergelist #%d%s\n",
				who_am_i(), i, lp->debug_type);
		}

		pop_filter_list(lp);
		if (i >= old_mergelist_cnt && lp->head) {
			/* This mergelist does not exist in the state to be restored, but it
			 * still has inherited rules.  This can sometimes happen if a per-dir
			 * merge file calls setup_merge_file() in push_local_filters() and that
			 * leaves some inherited rules that aren't in the pushed list state. */
			if (DEBUG_GTE(FILTER, 2)) {
				rprintf(FINFO, "[%s] freeing parent_dirscan filters of mergelist #%d%s\n",
					who_am_i(), i, ex->u.mergelist->debug_type);
			}
			pop_filter_list(lp);
		}
	}

	if (!pop)
		return; /* No state to restore. */

	for (i = 0; i < old_mergelist_cnt; i++) {
		filter_rule *ex = mergelist_parents[i];
		if (!ex)
			continue;
		memcpy(ex->u.mergelist, &pop->mergelists[i], sizeof (filter_rule_list));
	}

	free(pop);
}

void change_local_filter_dir(const char *dname, int dlen, int dir_depth)
{
	static int cur_depth = -1;
	static void *filt_array[MAXPATHLEN/2+1];

	if (!dname) {
		for ( ; cur_depth >= 0; cur_depth--) {
			if (filt_array[cur_depth]) {
				pop_local_filters(filt_array[cur_depth]);
				filt_array[cur_depth] = NULL;
			}
		}
		return;
	}

	assert(dir_depth < MAXPATHLEN/2+1);

	for ( ; cur_depth >= dir_depth; cur_depth--) {
		if (filt_array[cur_depth]) {
			pop_local_filters(filt_array[cur_depth]);
			filt_array[cur_depth] = NULL;
		}
	}

	cur_depth = dir_depth;
	filt_array[cur_depth] = push_local_filters(dname, dlen);
}

static int rule_matches(const char *fname, filter_rule *ex, int name_flags)
{
	int slash_handling, str_cnt = 0, anchored_match = 0;
	int ret_match = ex->rflags & FILTRULE_NEGATE ? 0 : 1;
	char *p, *pattern = ex->pattern;
	const char *strings[16]; /* more than enough */
	const char *name = fname + (*fname == '/');

	if (!*name || ex->elide == cur_elide_value)
		return 0;

	if (!(name_flags & NAME_IS_XATTR) ^ !(ex->rflags & FILTRULE_XATTR))
		return 0;

	if (!ex->u.slash_cnt && !(ex->rflags & FILTRULE_WILD2)) {
		/* If the pattern does not have any slashes AND it does
		 * not have a "**" (which could match a slash), then we
		 * just match the name portion of the path. */
		if ((p = strrchr(name,'/')) != NULL)
			name = p+1;
	} else if (ex->rflags & FILTRULE_ABS_PATH && *fname != '/'
	    && curr_dir_len > module_dirlen + 1) {
		/* If we're matching against an absolute-path pattern,
		 * we need to prepend our full path info. */
		strings[str_cnt++] = curr_dir + module_dirlen + 1;
		strings[str_cnt++] = "/";
	} else if (ex->rflags & FILTRULE_WILD2_PREFIX && *fname != '/') {
		/* Allow "**"+"/" to match at the start of the string. */
		strings[str_cnt++] = "/";
	}
	strings[str_cnt++] = name;
	if (name_flags & NAME_IS_DIR) {
		/* Allow a trailing "/"+"***" to match the directory. */
		if (ex->rflags & FILTRULE_WILD3_SUFFIX)
			strings[str_cnt++] = "/";
	} else if (ex->rflags & FILTRULE_DIRECTORY)
		return !ret_match;
	strings[str_cnt] = NULL;

	if (*pattern == '/') {
		anchored_match = 1;
		pattern++;
	}

	if (!anchored_match && ex->u.slash_cnt
	    && !(ex->rflags & FILTRULE_WILD2)) {
		/* A non-anchored match with an infix slash and no "**"
		 * needs to match the last slash_cnt+1 name elements. */
		slash_handling = ex->u.slash_cnt + 1;
	} else if (!anchored_match && !(ex->rflags & FILTRULE_WILD2_PREFIX)
				   && ex->rflags & FILTRULE_WILD2) {
		/* A non-anchored match with an infix or trailing "**" (but not
		 * a prefixed "**") needs to try matching after every slash. */
		slash_handling = -1;
	} else {
		/* The pattern matches only at the start of the path or name. */
		slash_handling = 0;
	}

	if (ex->rflags & FILTRULE_WILD) {
		if (wildmatch_array(pattern, strings, slash_handling))
			return ret_match;
	} else if (str_cnt > 1) {
		if (litmatch_array(pattern, strings, slash_handling))
			return ret_match;
	} else if (anchored_match) {
		if (strcmp(name, pattern) == 0)
			return ret_match;
	} else {
		int l1 = strlen(name);
		int l2 = strlen(pattern);
		if (l2 <= l1 &&
		    strcmp(name+(l1-l2),pattern) == 0 &&
		    (l1==l2 || name[l1-(l2+1)] == '/')) {
			return ret_match;
		}
	}

	return !ret_match;
}

static void report_filter_result(enum logcode code, char const *name,
				 filter_rule const *ent,
				 int name_flags, const char *type)
{
	int log_level = am_sender || am_generator ? 1 : 3;

	/* If a trailing slash is present to match only directories,
	 * then it is stripped out by add_rule().  So as a special
	 * case we add it back in the log output. */
	if (DEBUG_GTE(FILTER, log_level)) {
		static char *actions[2][2]
		    = { {"show", "hid"}, {"risk", "protect"} };
		const char *w = who_am_i();
		const char *t = name_flags & NAME_IS_XATTR ? "xattr"
			      : name_flags & NAME_IS_DIR ? "directory"
			      : "file";
		rprintf(code, "[%s] %sing %s %s because of pattern %s%s%s\n",
		    w, actions[*w=='g'][!(ent->rflags & FILTRULE_INCLUDE)],
		    t, name, ent->pattern,
		    ent->rflags & FILTRULE_DIRECTORY ? "/" : "", type);
	}
}

/* This function is used to check if a file should be included/excluded
 * from the list of files based on its name and type etc.  The value of
 * filter_level is set to either SERVER_FILTERS or ALL_FILTERS. */
int name_is_excluded(const char *fname, int name_flags, int filter_level)
{
	if (daemon_filter_list.head && check_filter(&daemon_filter_list, FLOG, fname, name_flags) < 0) {
		if (!(name_flags & NAME_IS_XATTR))
			errno = ENOENT;
		return 1;
	}

	if (filter_level != ALL_FILTERS)
		return 0;

	if (filter_list.head && check_filter(&filter_list, FINFO, fname, name_flags) < 0)
		return 1;

	return 0;
}

int check_server_filter(filter_rule_list *listp, enum logcode code, const char *name, int name_flags)
{
	int ret;
	cur_elide_value = LOCAL_RULE;
	ret = check_filter(listp, code, name, name_flags);
	cur_elide_value = REMOTE_RULE;
	return ret;
}

/* Return -1 if file "name" is defined to be excluded by the specified
 * exclude list, 1 if it is included, and 0 if it was not matched. */
int check_filter(filter_rule_list *listp, enum logcode code,
		 const char *name, int name_flags)
{
	filter_rule *ent;

	for (ent = listp->head; ent; ent = ent->next) {
		if (ignore_perishable && ent->rflags & FILTRULE_PERISHABLE)
			continue;
		if (ent->rflags & FILTRULE_PERDIR_MERGE) {
			int rc = check_filter(ent->u.mergelist, code, name, name_flags);
			if (rc)
				return rc;
			continue;
		}
		if (ent->rflags & FILTRULE_CVS_IGNORE) {
			int rc = check_filter(&cvs_filter_list, code, name, name_flags);
			if (rc)
				return rc;
			continue;
		}
		if (rule_matches(name, ent, name_flags)) {
			report_filter_result(code, name, ent, name_flags, listp->debug_type);
			return ent->rflags & FILTRULE_INCLUDE ? 1 : -1;
		}
	}

	return 0;
}

#define RULE_STRCMP(s,r) rule_strcmp((s), (r), sizeof (r) - 1)

static const uchar *rule_strcmp(const uchar *str, const char *rule, int rule_len)
{
	if (strncmp((char*)str, rule, rule_len) != 0)
		return NULL;
	if (isspace(str[rule_len]) || str[rule_len] == '_' || !str[rule_len])
		return str + rule_len - 1;
	if (str[rule_len] == ',')
		return str + rule_len;
	return NULL;
}

#define FILTRULES_FROM_CONTAINER (FILTRULE_ABS_PATH | FILTRULE_INCLUDE \
				| FILTRULE_DIRECTORY | FILTRULE_NEGATE \
				| FILTRULE_PERISHABLE)

/* Gets the next include/exclude rule from *rulestr_ptr and advances
 * *rulestr_ptr to point beyond it.  Stores the pattern's start (within
 * *rulestr_ptr) and length in *pat_ptr and *pat_len_ptr, and returns a newly
 * allocated filter_rule containing the rest of the information.  Returns
 * NULL if there are no more rules in the input.
 *
 * The template provides defaults for the new rule to inherit, and the
 * template rflags and the xflags additionally affect parsing. */
static filter_rule *parse_rule_tok(const char **rulestr_ptr,
				   const filter_rule *template, int xflags,
				   const char **pat_ptr, unsigned int *pat_len_ptr)
{
	const uchar *s = (const uchar *)*rulestr_ptr;
	filter_rule *rule;
	unsigned int len;

	if (template->rflags & FILTRULE_WORD_SPLIT) {
		/* Skip over any initial whitespace. */
		while (isspace(*s))
			s++;
		/* Update to point to real start of rule. */
		*rulestr_ptr = (const char *)s;
	}
	if (!*s)
		return NULL;

	rule = new0(filter_rule);

	/* Inherit from the template.  Don't inherit FILTRULES_SIDES; we check
	 * that later. */
	rule->rflags = template->rflags & FILTRULES_FROM_CONTAINER;

	/* Figure out what kind of a filter rule "s" is pointing at.  Note
	 * that if FILTRULE_NO_PREFIXES is set, the rule is either an include
	 * or an exclude based on the inheritance of the FILTRULE_INCLUDE
	 * flag (above).  XFLG_OLD_PREFIXES indicates a compatibility mode
	 * for old include/exclude patterns where just "+ " and "- " are
	 * allowed as optional prefixes.  */
	if (template->rflags & FILTRULE_NO_PREFIXES) {
		if (*s == '!' && template->rflags & FILTRULE_CVS_IGNORE)
			rule->rflags |= FILTRULE_CLEAR_LIST; /* Tentative! */
	} else if (xflags & XFLG_OLD_PREFIXES) {
		if (*s == '-' && s[1] == ' ') {
			rule->rflags &= ~FILTRULE_INCLUDE;
			s += 2;
		} else if (*s == '+' && s[1] == ' ') {
			rule->rflags |= FILTRULE_INCLUDE;
			s += 2;
		} else if (*s == '!')
			rule->rflags |= FILTRULE_CLEAR_LIST; /* Tentative! */
	} else {
		char ch = 0;
		BOOL prefix_specifies_side = False;
		switch (*s) {
		case 'c':
			if ((s = RULE_STRCMP(s, "clear")) != NULL)
				ch = '!';
			break;
		case 'd':
			if ((s = RULE_STRCMP(s, "dir-merge")) != NULL)
				ch = ':';
			break;
		case 'e':
			if ((s = RULE_STRCMP(s, "exclude")) != NULL)
				ch = '-';
			break;
		case 'h':
			if ((s = RULE_STRCMP(s, "hide")) != NULL)
				ch = 'H';
			break;
		case 'i':
			if ((s = RULE_STRCMP(s, "include")) != NULL)
				ch = '+';
			break;
		case 'm':
			if ((s = RULE_STRCMP(s, "merge")) != NULL)
				ch = '.';
			break;
		case 'p':
			if ((s = RULE_STRCMP(s, "protect")) != NULL)
				ch = 'P';
			break;
		case 'r':
			if ((s = RULE_STRCMP(s, "risk")) != NULL)
				ch = 'R';
			break;
		case 's':
			if ((s = RULE_STRCMP(s, "show")) != NULL)
				ch = 'S';
			break;
		default:
			ch = *s;
			if (s[1] == ',')
				s++;
			break;
		}
		switch (ch) {
		case ':':
			trust_sender_filter = 1;
			rule->rflags |= FILTRULE_PERDIR_MERGE
				      | FILTRULE_FINISH_SETUP;
			/* FALL THROUGH */
		case '.':
			rule->rflags |= FILTRULE_MERGE_FILE;
			break;
		case '+':
			rule->rflags |= FILTRULE_INCLUDE;
			break;
		case '-':
			break;
		case 'S':
			rule->rflags |= FILTRULE_INCLUDE;
			/* FALL THROUGH */
		case 'H':
			rule->rflags |= FILTRULE_SENDER_SIDE;
			prefix_specifies_side = True;
			break;
		case 'R':
			rule->rflags |= FILTRULE_INCLUDE;
			/* FALL THROUGH */
		case 'P':
			rule->rflags |= FILTRULE_RECEIVER_SIDE;
			prefix_specifies_side = True;
			break;
		case '!':
			rule->rflags |= FILTRULE_CLEAR_LIST;
			break;
		default:
			rprintf(FERROR, "Unknown filter rule: `%s'\n", *rulestr_ptr);
			exit_cleanup(RERR_SYNTAX);
		}
		while (ch != '!' && *++s && *s != ' ' && *s != '_') {
			if (template->rflags & FILTRULE_WORD_SPLIT && isspace(*s)) {
				s--;
				break;
			}
			switch (*s) {
			default:
			    invalid:
				rprintf(FERROR,
					"invalid modifier '%c' at position %d in filter rule: %s\n",
					*s, (int)(s - (const uchar *)*rulestr_ptr), *rulestr_ptr);
				exit_cleanup(RERR_SYNTAX);
			case '-':
				if (!BITS_SETnUNSET(rule->rflags, FILTRULE_MERGE_FILE, FILTRULE_NO_PREFIXES))
					goto invalid;
				rule->rflags |= FILTRULE_NO_PREFIXES;
				break;
			case '+':
				if (!BITS_SETnUNSET(rule->rflags, FILTRULE_MERGE_FILE, FILTRULE_NO_PREFIXES))
					goto invalid;
				rule->rflags |= FILTRULE_NO_PREFIXES
					      | FILTRULE_INCLUDE;
				break;
			case '/':
				rule->rflags |= FILTRULE_ABS_PATH;
				break;
			case '!':
				/* Negation really goes with the pattern, so it
				 * isn't useful as a merge-file default. */
				if (rule->rflags & FILTRULE_MERGE_FILE)
					goto invalid;
				rule->rflags |= FILTRULE_NEGATE;
				break;
			case 'C':
				if (rule->rflags & FILTRULE_NO_PREFIXES || prefix_specifies_side)
					goto invalid;
				rule->rflags |= FILTRULE_NO_PREFIXES
					      | FILTRULE_WORD_SPLIT
					      | FILTRULE_NO_INHERIT
					      | FILTRULE_CVS_IGNORE;
				break;
			case 'e':
				if (!(rule->rflags & FILTRULE_MERGE_FILE))
					goto invalid;
				rule->rflags |= FILTRULE_EXCLUDE_SELF;
				break;
			case 'n':
				if (!(rule->rflags & FILTRULE_MERGE_FILE))
					goto invalid;
				rule->rflags |= FILTRULE_NO_INHERIT;
				break;
			case 'p':
				rule->rflags |= FILTRULE_PERISHABLE;
				break;
			case 'r':
				if (prefix_specifies_side)
					goto invalid;
				rule->rflags |= FILTRULE_RECEIVER_SIDE;
				break;
			case 's':
				if (prefix_specifies_side)
					goto invalid;
				rule->rflags |= FILTRULE_SENDER_SIDE;
				break;
			case 'w':
				if (!(rule->rflags & FILTRULE_MERGE_FILE))
					goto invalid;
				rule->rflags |= FILTRULE_WORD_SPLIT;
				break;
			case 'x':
				rule->rflags |= FILTRULE_XATTR;
				saw_xattr_filter = 1;
				break;
			}
		}
		if (*s)
			s++;
	}
	if (template->rflags & FILTRULES_SIDES) {
		if (rule->rflags & FILTRULES_SIDES) {
			/* The filter and template both specify side(s).  This
			 * is dodgy (and won't work correctly if the template is
			 * a one-sided per-dir merge rule), so reject it. */
			rprintf(FERROR,
				"specified-side merge file contains specified-side filter: %s\n",
				*rulestr_ptr);
			exit_cleanup(RERR_SYNTAX);
		}
		rule->rflags |= template->rflags & FILTRULES_SIDES;
	}

	if (template->rflags & FILTRULE_WORD_SPLIT) {
		const uchar *cp = s;
		/* Token ends at whitespace or the end of the string. */
		while (!isspace(*cp) && *cp != '\0')
			cp++;
		len = cp - s;
	} else
		len = strlen((char*)s);

	if (rule->rflags & FILTRULE_CLEAR_LIST) {
		if (!(rule->rflags & FILTRULE_NO_PREFIXES)
		 && !(xflags & XFLG_OLD_PREFIXES) && len) {
			rprintf(FERROR,
				"'!' rule has trailing characters: %s\n", *rulestr_ptr);
			exit_cleanup(RERR_SYNTAX);
		}
		if (len > 1)
			rule->rflags &= ~FILTRULE_CLEAR_LIST;
	} else if (!len && !(rule->rflags & FILTRULE_CVS_IGNORE)) {
		rprintf(FERROR, "unexpected end of filter rule: %s\n", *rulestr_ptr);
		exit_cleanup(RERR_SYNTAX);
	}

	/* --delete-excluded turns an un-modified include/exclude into a sender-side rule.  */
	if (delete_excluded
	 && !(rule->rflags & (FILTRULES_SIDES|FILTRULE_MERGE_FILE|FILTRULE_PERDIR_MERGE)))
		rule->rflags |= FILTRULE_SENDER_SIDE;

	*pat_ptr = (const char *)s;
	*pat_len_ptr = len;
	*rulestr_ptr = *pat_ptr + len;
	return rule;
}

static void get_cvs_excludes(uint32 rflags)
{
	static int initialized = 0;
	char *p, fname[MAXPATHLEN];

	if (initialized)
		return;
	initialized = 1;

	parse_filter_str(&cvs_filter_list, default_cvsignore(),
			 rule_template(rflags | (protocol_version >= 30 ? FILTRULE_PERISHABLE : 0)),
			 0);

	p = module_id >= 0 && lp_use_chroot(module_id) ? "/" : getenv("HOME");
	if (p && pathjoin(fname, MAXPATHLEN, p, ".cvsignore") < MAXPATHLEN)
		parse_filter_file(&cvs_filter_list, fname, rule_template(rflags), 0);

	parse_filter_str(&cvs_filter_list, getenv("CVSIGNORE"), rule_template(rflags), 0);
}

const filter_rule *rule_template(uint32 rflags)
{
	static filter_rule template; /* zero-initialized */
	template.rflags = rflags;
	return &template;
}

void parse_filter_str(filter_rule_list *listp, const char *rulestr,
		     const filter_rule *template, int xflags)
{
	filter_rule *rule;
	const char *pat;
	unsigned int pat_len;

	if (!rulestr)
		return;

	while (1) {
		uint32 new_rflags;

		/* Remember that the returned string is NOT '\0' terminated! */
		if (!(rule = parse_rule_tok(&rulestr, template, xflags, &pat, &pat_len)))
			break;

		if (pat_len >= MAXPATHLEN) {
			rprintf(FERROR, "discarding over-long filter: %.*s\n",
				(int)pat_len, pat);
		    free_continue:
			free_filter(rule);
			continue;
		}

		new_rflags = rule->rflags;
		if (new_rflags & FILTRULE_CLEAR_LIST) {
			if (DEBUG_GTE(FILTER, 2)) {
				rprintf(FINFO,
					"[%s] clearing filter list%s\n",
					who_am_i(), listp->debug_type);
			}
			pop_filter_list(listp);
			listp->head = NULL;
			goto free_continue;
		}

		if (new_rflags & FILTRULE_MERGE_FILE) {
			if (!pat_len) {
				pat = ".cvsignore";
				pat_len = 10;
			}
			if (new_rflags & FILTRULE_EXCLUDE_SELF) {
				const char *name;
				filter_rule *excl_self;

				excl_self = new0(filter_rule);
				/* Find the beginning of the basename and add an exclude for it. */
				for (name = pat + pat_len; name > pat && name[-1] != '/'; name--) {}
				add_rule(listp, name, (pat + pat_len) - name, excl_self, 0);
				rule->rflags &= ~FILTRULE_EXCLUDE_SELF;
			}
			if (new_rflags & FILTRULE_PERDIR_MERGE) {
				if (parent_dirscan) {
					const char *p;
					unsigned int len = pat_len;
					if ((p = parse_merge_name(pat, &len, module_dirlen)))
						add_rule(listp, p, len, rule, 0);
					else
						free_filter(rule);
					continue;
				}
			} else {
				const char *p;
				unsigned int len = pat_len;
				if ((p = parse_merge_name(pat, &len, 0)))
					parse_filter_file(listp, p, rule, XFLG_FATAL_ERRORS);
				free_filter(rule);
				continue;
			}
		}

		add_rule(listp, pat, pat_len, rule, xflags);

		if (new_rflags & FILTRULE_CVS_IGNORE
		    && !(new_rflags & FILTRULE_MERGE_FILE))
			get_cvs_excludes(new_rflags);
	}
}

void parse_filter_file(filter_rule_list *listp, const char *fname, const filter_rule *template, int xflags)
{
	FILE *fp;
	char line[BIGPATHBUFLEN];
	char *eob = line + sizeof line - 1;
	BOOL word_split = (template->rflags & FILTRULE_WORD_SPLIT) != 0;

	if (!fname || !*fname)
		return;

	if (*fname != '-' || fname[1] || am_server) {
		if (daemon_filter_list.head) {
			strlcpy(line, fname, sizeof line);
			clean_fname(line, CFN_COLLAPSE_DOT_DOT_DIRS);
			if (check_filter(&daemon_filter_list, FLOG, line, 0) < 0)
				fp = NULL;
			else
				fp = fopen(line, "rb");
		} else
			fp = fopen(fname, "rb");
	} else
		fp = stdin;

	if (DEBUG_GTE(FILTER, 2)) {
		rprintf(FINFO, "[%s] parse_filter_file(%s,%x,%x)%s\n",
			who_am_i(), fname, template->rflags, xflags,
			fp ? "" : " [not found]");
	}

	if (!fp) {
		if (xflags & XFLG_FATAL_ERRORS) {
			rsyserr(FERROR, errno,
				"failed to open %sclude file %s",
				template->rflags & FILTRULE_INCLUDE ? "in" : "ex",
				fname);
			exit_cleanup(RERR_FILEIO);
		}
		return;
	}
	dirbuf[dirbuf_len] = '\0';

	while (1) {
		char *s = line;
		int ch, overflow = 0;
		while (1) {
			if ((ch = getc(fp)) == EOF) {
				if (ferror(fp) && errno == EINTR) {
					clearerr(fp);
					continue;
				}
				break;
			}
			if (word_split && isspace(ch))
				break;
			if (eol_nulls? !ch : (ch == '\n' || ch == '\r'))
				break;
			if (s < eob)
				*s++ = ch;
			else
				overflow = 1;
		}
		if (overflow) {
			rprintf(FERROR, "discarding over-long filter: %s...\n", line);
			s = line;
		}
		*s = '\0';
		/* Skip an empty token and (when line parsing) comments. */
		if (*line && (word_split || (*line != ';' && *line != '#')))
			parse_filter_str(listp, line, template, xflags);
		if (ch == EOF)
			break;
	}
	fclose(fp);
}

/* If the "for_xfer" flag is set, the prefix is made compatible with the
 * current protocol_version (if possible) or a NULL is returned (if not
 * possible). */
char *get_rule_prefix(filter_rule *rule, const char *pat, int for_xfer,
		      unsigned int *plen_ptr)
{
	static char buf[MAX_RULE_PREFIX+1];
	char *op = buf;
	int legal_len = for_xfer && protocol_version < 29 ? 1 : MAX_RULE_PREFIX-1;

	if (rule->rflags & FILTRULE_PERDIR_MERGE) {
		if (legal_len == 1)
			return NULL;
		*op++ = ':';
	} else if (rule->rflags & FILTRULE_INCLUDE)
		*op++ = '+';
	else if (legal_len != 1
	    || ((*pat == '-' || *pat == '+') && pat[1] == ' '))
		*op++ = '-';
	else
		legal_len = 0;

	if (rule->rflags & FILTRULE_ABS_PATH)
		*op++ = '/';
	if (rule->rflags & FILTRULE_NEGATE)
		*op++ = '!';
	if (rule->rflags & FILTRULE_CVS_IGNORE)
		*op++ = 'C';
	else {
		if (rule->rflags & FILTRULE_NO_INHERIT)
			*op++ = 'n';
		if (rule->rflags & FILTRULE_WORD_SPLIT)
			*op++ = 'w';
		if (rule->rflags & FILTRULE_NO_PREFIXES) {
			if (rule->rflags & FILTRULE_INCLUDE)
				*op++ = '+';
			else
				*op++ = '-';
		}
	}
	if (rule->rflags & FILTRULE_EXCLUDE_SELF)
		*op++ = 'e';
	if (rule->rflags & FILTRULE_XATTR)
		*op++ = 'x';
	if (rule->rflags & FILTRULE_SENDER_SIDE
	    && (!for_xfer || protocol_version >= 29))
		*op++ = 's';
	if (rule->rflags & FILTRULE_RECEIVER_SIDE
	    && (!for_xfer || protocol_version >= 29
	     || (delete_excluded && am_sender)))
		*op++ = 'r';
	if (rule->rflags & FILTRULE_PERISHABLE) {
		if (!for_xfer || protocol_version >= 30)
			*op++ = 'p';
		else if (am_sender)
			return NULL;
	}
	if (op - buf > legal_len)
		return NULL;
	if (legal_len)
		*op++ = ' ';
	*op = '\0';
	if (plen_ptr)
		*plen_ptr = op - buf;
	return buf;
}

static void send_rules(int f_out, filter_rule_list *flp)
{
	filter_rule *ent;

	for (ent = flp->head; ent; ent = ent->next) {
		unsigned int len, plen, dlen;
		int elide = 0;
		char *p;

		/* Note we need to check delete_excluded here in addition to
		 * the code in parse_rule_tok() because some rules may have
		 * been added before we found the --delete-excluded option.
		 * We must also elide any CVS merge-file rules to avoid a
		 * backward compatibility problem, and we elide any no-prefix
		 * merge files as an optimization (since they can only have
		 * include/exclude rules). */
		if (ent->rflags & FILTRULE_SENDER_SIDE)
			elide = am_sender ? LOCAL_RULE : REMOTE_RULE;
		if (ent->rflags & FILTRULE_RECEIVER_SIDE)
			elide = elide ? 0 : am_sender ? REMOTE_RULE : LOCAL_RULE;
		else if (delete_excluded && !elide
		 && (!(ent->rflags & FILTRULE_PERDIR_MERGE)
		  || ent->rflags & FILTRULE_NO_PREFIXES))
			elide = am_sender ? LOCAL_RULE : REMOTE_RULE;
		ent->elide = elide;
		if (elide == LOCAL_RULE)
			continue;
		if (ent->rflags & FILTRULE_CVS_IGNORE
		    && !(ent->rflags & FILTRULE_MERGE_FILE)) {
			int f = am_sender || protocol_version < 29 ? f_out : -2;
			send_rules(f, &cvs_filter_list);
			if (f == f_out)
				continue;
		}
		p = get_rule_prefix(ent, ent->pattern, 1, &plen);
		if (!p) {
			rprintf(FERROR,
				"filter rules are too modern for remote rsync.\n");
			exit_cleanup(RERR_PROTOCOL);
		}
		if (f_out < 0)
			continue;
		len = strlen(ent->pattern);
		dlen = ent->rflags & FILTRULE_DIRECTORY ? 1 : 0;
		if (!(plen + len + dlen))
			continue;
		write_int(f_out, plen + len + dlen);
		if (plen)
			write_buf(f_out, p, plen);
		write_buf(f_out, ent->pattern, len);
		if (dlen)
			write_byte(f_out, '/');
	}
}

/* This is only called by the client. */
void send_filter_list(int f_out)
{
	int receiver_wants_list = prune_empty_dirs
	    || (delete_mode && (!delete_excluded || protocol_version >= 29));

	if (local_server || (am_sender && !receiver_wants_list))
		f_out = -1;
	if (cvs_exclude && am_sender) {
		if (protocol_version >= 29)
			parse_filter_str(&filter_list, ":C", rule_template(0), 0);
		parse_filter_str(&filter_list, "-C", rule_template(0), 0);
	}

	send_rules(f_out, &filter_list);

	if (f_out >= 0)
		write_int(f_out, 0);

	if (cvs_exclude) {
		if (!am_sender || protocol_version < 29)
			parse_filter_str(&filter_list, ":C", rule_template(0), 0);
		if (!am_sender)
			parse_filter_str(&filter_list, "-C", rule_template(0), 0);
	}
}

/* This is only called by the server. */
void recv_filter_list(int f_in)
{
	char line[BIGPATHBUFLEN];
	int xflags = protocol_version >= 29 ? 0 : XFLG_OLD_PREFIXES;
	int receiver_wants_list = prune_empty_dirs
	    || (delete_mode && (!delete_excluded || protocol_version >= 29));
	unsigned int len;

	if (!local_server && (am_sender || receiver_wants_list)) {
		while ((len = read_int(f_in)) != 0) {
			if (len >= sizeof line)
				overflow_exit("recv_rules");
			read_sbuf(f_in, line, len);
			parse_filter_str(&filter_list, line, rule_template(0), xflags);
		}
	}

	if (cvs_exclude) {
		if (local_server || am_sender || protocol_version < 29)
			parse_filter_str(&filter_list, ":C", rule_template(0), 0);
		if (local_server || am_sender)
			parse_filter_str(&filter_list, "-C", rule_template(0), 0);
	}

	if (local_server) /* filter out any rules that aren't for us. */
		send_rules(-1, &filter_list);
}
/*
 * File IO utilities used in rsync.
 *
 * Copyright (C) 1998 Andrew Tridgell
 * Copyright (C) 2002 Martin Pool
 * Copyright (C) 2004-2023 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"
#include "inums.h"

#ifndef ENODATA
#define ENODATA EAGAIN
#endif

/* We want all reads to be aligned on 1K boundaries. */
#define ALIGN_BOUNDARY 1024
/* How far past the boundary is an offset? */
#define ALIGNED_OVERSHOOT(oft) ((oft) & (ALIGN_BOUNDARY-1))
/* Round up a length to the next boundary */
#define ALIGNED_LENGTH(len) ((((len) - 1) | (ALIGN_BOUNDARY-1)) + 1)

extern int sparse_files;

OFF_T preallocated_len = 0;

static OFF_T sparse_seek = 0;
static OFF_T sparse_past_write = 0;

int sparse_end(int f, OFF_T size, int updating_basis_or_equiv)
{
	int ret = 0;

	if (updating_basis_or_equiv) {
		if (sparse_seek && do_punch_hole(f, sparse_past_write, sparse_seek) < 0)
			ret = -1;
#ifdef HAVE_FTRUNCATE /* A compilation formality -- in-place requires ftruncate() */
		else /* Just in case the original file was longer */
			ret = do_ftruncate(f, size);
#endif
	} else if (sparse_seek) {
#ifdef HAVE_FTRUNCATE
		ret = do_ftruncate(f, size);
#else
		if (do_lseek(f, sparse_seek-1, SEEK_CUR) != size-1)
			ret = -1;
		else {
			do {
				ret = write(f, "", 1);
			} while (ret < 0 && errno == EINTR);

			ret = ret <= 0 ? -1 : 0;
		}
#endif
	}

	sparse_past_write = sparse_seek = 0;

	return ret;
}

/* Note that the offset is just the caller letting us know where
 * the current file position is in the file. The use_seek arg tells
 * us that we should seek over matching data instead of writing it. */
static int write_sparse(int f, int use_seek, OFF_T offset, const char *buf, int len)
{
	int l1 = 0, l2 = 0;
	int ret;

	for (l1 = 0; l1 < len && buf[l1] == 0; l1++) {}
	for (l2 = 0; l2 < len-l1 && buf[len-(l2+1)] == 0; l2++) {}

	sparse_seek += l1;

	if (l1 == len)
		return len;

	if (sparse_seek) {
		if (sparse_past_write >= preallocated_len) {
			if (do_lseek(f, sparse_seek, SEEK_CUR) < 0)
				return -1;
		} else if (do_punch_hole(f, sparse_past_write, sparse_seek) < 0) {
			sparse_seek = 0;
			return -1;
		}
	}
	sparse_seek = l2;
	sparse_past_write = offset + len - l2;

	if (use_seek) {
		/* The in-place data already matches. */
		if (do_lseek(f, len - (l1+l2), SEEK_CUR) < 0)
			return -1;
		return len;
	}

	while ((ret = write(f, buf + l1, len - (l1+l2))) <= 0) {
		if (ret < 0 && errno == EINTR)
			continue;
		sparse_seek = 0;
		return ret;
	}

	if (ret != (int)(len - (l1+l2))) {
		sparse_seek = 0;
		return l1+ret;
	}

	return len;
}

static char *wf_writeBuf;
static size_t wf_writeBufSize;
static size_t wf_writeBufCnt;

int flush_write_file(int f)
{
	int ret = 0;
	char *bp = wf_writeBuf;

	while (wf_writeBufCnt > 0) {
		if ((ret = write(f, bp, wf_writeBufCnt)) < 0) {
			if (errno == EINTR)
				continue;
			return ret;
		}
		wf_writeBufCnt -= ret;
		bp += ret;
	}
	return ret;
}

/* write_file does not allow incomplete writes.  It loops internally
 * until len bytes are written or errno is set.  Note that use_seek and
 * offset are only used in sparse processing (see write_sparse()). */
int write_file(int f, int use_seek, OFF_T offset, const char *buf, int len)
{
	int ret = 0;

	while (len > 0) {
		int r1;
		if (sparse_files > 0) {
			int len1 = MIN(len, SPARSE_WRITE_SIZE);
			r1 = write_sparse(f, use_seek, offset, buf, len1);
			offset += r1;
		} else {
			if (!wf_writeBuf) {
				wf_writeBufSize = WRITE_SIZE * 8;
				wf_writeBufCnt  = 0;
				wf_writeBuf = new_array(char, wf_writeBufSize);
			}
			r1 = (int)MIN((size_t)len, wf_writeBufSize - wf_writeBufCnt);
			if (r1) {
				memcpy(wf_writeBuf + wf_writeBufCnt, buf, r1);
				wf_writeBufCnt += r1;
			}
			if (wf_writeBufCnt == wf_writeBufSize) {
				if (flush_write_file(f) < 0)
					return -1;
				if (!r1 && len)
					continue;
			}
		}
		if (r1 <= 0) {
			if (ret > 0)
				return ret;
			return r1;
		}
		len -= r1;
		buf += r1;
		ret += r1;
	}
	return ret;
}

/* An in-place update found identical data at an identical location. We either
 * just seek past it, or (for an in-place sparse update), we give the data to
 * the sparse processor with the use_seek flag set. */
int skip_matched(int fd, OFF_T offset, const char *buf, int len)
{
	OFF_T pos;

	if (sparse_files > 0) {
		if (write_file(fd, 1, offset, buf, len) != len)
			return -1;
		return 0;
	}

	if (flush_write_file(fd) < 0)
		return -1;

	if ((pos = do_lseek(fd, len, SEEK_CUR)) != offset + len) {
		rsyserr(FERROR_XFER, errno, "lseek returned %s, not %s",
			big_num(pos), big_num(offset));
		return -1;
	}

	return 0;
}

/* This provides functionality somewhat similar to mmap() but using read().
 * It gives sliding window access to a file.  mmap() is not used because of
 * the possibility of another program (such as a mailer) truncating the
 * file thus giving us a SIGBUS. */
struct map_struct *map_file(int fd, OFF_T len, int32 read_size, int32 blk_size)
{
	struct map_struct *map;

	map = new0(struct map_struct);

	if (blk_size && (read_size % blk_size))
		read_size += blk_size - (read_size % blk_size);

	map->fd = fd;
	map->file_size = len;
	map->def_window_size = ALIGNED_LENGTH(read_size);

	return map;
}


/* slide the read window in the file */
char *map_ptr(struct map_struct *map, OFF_T offset, int32 len)
{
	OFF_T window_start, read_start;
	int32 window_size, read_size, read_offset, align_fudge;

	if (len == 0)
		return NULL;
	if (len < 0) {
		rprintf(FERROR, "invalid len passed to map_ptr: %ld\n",
			(long)len);
		exit_cleanup(RERR_FILEIO);
	}

	/* in most cases the region will already be available */
	if (offset >= map->p_offset && offset+len <= map->p_offset+map->p_len)
		return map->p + (offset - map->p_offset);

	/* nope, we are going to have to do a read. Work out our desired window */
	align_fudge = (int32)ALIGNED_OVERSHOOT(offset);
	window_start = offset - align_fudge;
	window_size = map->def_window_size;
	if (window_start + window_size > map->file_size)
		window_size = (int32)(map->file_size - window_start);
	if (window_size < len + align_fudge)
		window_size = ALIGNED_LENGTH(len + align_fudge);

	/* make sure we have allocated enough memory for the window */
	if (window_size > map->p_size) {
		map->p = realloc_array(map->p, char, window_size);
		map->p_size = window_size;
	}

	/* Now try to avoid re-reading any bytes by reusing any bytes from the previous buffer. */
	if (window_start >= map->p_offset && window_start < map->p_offset + map->p_len
	 && window_start + window_size >= map->p_offset + map->p_len) {
		read_start = map->p_offset + map->p_len;
		read_offset = (int32)(read_start - window_start);
		read_size = window_size - read_offset;
		memmove(map->p, map->p + (map->p_len - read_offset), read_offset);
	} else {
		read_start = window_start;
		read_size = window_size;
		read_offset = 0;
	}

	if (read_size <= 0) {
		rprintf(FERROR, "invalid read_size of %ld in map_ptr\n",
			(long)read_size);
		exit_cleanup(RERR_FILEIO);
	}

	if (map->p_fd_offset != read_start) {
		OFF_T ret = do_lseek(map->fd, read_start, SEEK_SET);
		if (ret != read_start) {
			rsyserr(FERROR, errno, "lseek returned %s, not %s",
				big_num(ret), big_num(read_start));
			exit_cleanup(RERR_FILEIO);
		}
		map->p_fd_offset = read_start;
	}
	map->p_offset = window_start;
	map->p_len = window_size;

	while (read_size > 0) {
		int32 nread = read(map->fd, map->p + read_offset, read_size);
		if (nread <= 0) {
			if (!map->status)
				map->status = nread ? errno : ENODATA;
			/* The best we can do is zero the buffer -- the file
			 * has changed mid transfer! */
			memset(map->p + read_offset, 0, read_size);
			break;
		}
		map->p_fd_offset += nread;
		read_offset += nread;
		read_size -= nread;
	}

	return map->p + align_fudge;
}

int unmap_file(struct map_struct *map)
{
	int	ret;

	if (map->p) {
		free(map->p);
		map->p = NULL;
	}
	ret = map->status;
#if 0 /* I don't think we really need this. */
	force_memzero(map, sizeof map[0]);
#endif
	free(map);

	return ret;
}
/*
 * Generate and receive file lists.
 *
 * Copyright (C) 1996 Andrew Tridgell
 * Copyright (C) 1996 Paul Mackerras
 * Copyright (C) 2001, 2002 Martin Pool <mbp@samba.org>
 * Copyright (C) 2002-2023 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"
#include "ifuncs.h"
#include "rounding.h"
#include "inums.h"
#include "io.h"

extern int am_root;
extern int am_server;
extern int am_daemon;
extern int am_sender;
extern int am_generator;
extern int inc_recurse;
extern int always_checksum;
extern int module_id;
extern int ignore_errors;
extern int numeric_ids;
extern int quiet;
extern int recurse;
extern int use_qsort;
extern int xfer_dirs;
extern int filesfrom_fd;
extern int one_file_system;
extern int copy_devices;
extern int copy_dirlinks;
extern int preserve_uid;
extern int preserve_gid;
extern int preserve_acls;
extern int preserve_xattrs;
extern int preserve_links;
extern int preserve_hard_links;
extern int preserve_devices;
extern int preserve_specials;
extern int delete_during;
extern int missing_args;
extern int eol_nulls;
extern int atimes_ndx;
extern int crtimes_ndx;
extern int relative_paths;
extern int implied_dirs;
extern int ignore_perishable;
extern int non_perishable_cnt;
extern int prune_empty_dirs;
extern int copy_links;
extern int copy_unsafe_links;
extern int protocol_version;
extern int sanitize_paths;
extern int munge_symlinks;
extern int use_safe_inc_flist;
extern int need_unsorted_flist;
extern int sender_symlink_iconv;
extern int output_needs_newline;
extern int sender_keeps_checksum;
extern int trust_sender_filter;
extern int unsort_ndx;
extern uid_t our_uid;
extern struct stats stats;
extern char *filesfrom_host;
extern char *usermap, *groupmap;

extern struct name_num_item *file_sum_nni;

extern char curr_dir[MAXPATHLEN];

extern struct chmod_mode_struct *chmod_modes;

extern filter_rule_list filter_list, implied_filter_list, daemon_filter_list;

#ifdef ICONV_OPTION
extern int filesfrom_convert;
extern iconv_t ic_send, ic_recv;
#endif

#define PTR_SIZE (sizeof (struct file_struct *))

int io_error;
int flist_csum_len;
dev_t filesystem_dev; /* used to implement -x */

struct file_list *cur_flist, *first_flist, *dir_flist;
int send_dir_ndx = -1, send_dir_depth = -1;
int flist_cnt = 0; /* how many (non-tmp) file list objects exist */
int file_total = 0; /* total of all active items over all file-lists */
int file_old_total = 0; /* total of active items that will soon be gone */
int flist_eof = 0; /* all the file-lists are now known */
int xfer_flags_as_varint = 0;

#define NORMAL_NAME 0
#define SLASH_ENDING_NAME 1
#define DOTDIR_NAME 2
#define MISSING_NAME 3

/* Starting from protocol version 26, we always use 64-bit ino_t and dev_t
 * internally, even if this platform does not allow files to have 64-bit inums.
 * The only exception is if we're on a platform with no 64-bit type at all.
 *
 * Because we use read_longint() to get these off the wire, if you transfer
 * devices or (for protocols < 30) hardlinks with dev or inum > 2**32 to a
 * machine with no 64-bit types then you will get an overflow error.
 *
 * Note that if you transfer devices from a 64-bit-devt machine (say, Solaris)
 * to a 32-bit-devt machine (say, Linux-2.2/x86) then the device numbers will
 * be truncated.  But it's a kind of silly thing to do anyhow. */

/* The tmp_* vars are used as a cache area by make_file() to store data
 * that the sender doesn't need to remember in its file list.  The data
 * will survive just long enough to be used by send_file_entry(). */
static dev_t tmp_rdev;
#ifdef SUPPORT_HARD_LINKS
static int64 tmp_dev = -1, tmp_ino;
#endif
static char tmp_sum[MAX_DIGEST_LEN];

static char empty_sum[MAX_DIGEST_LEN];
static int flist_count_offset; /* for --delete --progress */
static int show_filelist_progress;

static struct file_list *flist_new(int flags, const char *msg);
static void flist_sort_and_clean(struct file_list *flist, int strip_root);
static void output_flist(struct file_list *flist);

void init_flist(void)
{
	if (DEBUG_GTE(FLIST, 4)) {
		rprintf(FINFO, "FILE_STRUCT_LEN=%d, EXTRA_LEN=%d\n",
			(int)FILE_STRUCT_LEN, (int)EXTRA_LEN);
	}
	/* Note that this isn't identical to file_sum_len in the case of CSUM_MD4_ARCHAIC: */
	flist_csum_len = csum_len_for_type(file_sum_nni->num, 1);

	show_filelist_progress = INFO_GTE(FLIST, 1) && xfer_dirs && !am_server && !inc_recurse;
}

static void start_filelist_progress(char *kind)
{
	if (quiet)
		return;
	rprintf(FCLIENT, "%s ... ", kind);
	output_needs_newline = 1;
	rflush(FINFO);
}

static void emit_filelist_progress(int count)
{
	if (quiet)
		return;
	if (output_needs_newline == 2) /* avoid a newline in the middle of this filelist-progress output */
		output_needs_newline = 0;
	rprintf(FCLIENT, " %d files...\r", count);
	output_needs_newline = 2;
}

static void maybe_emit_filelist_progress(int count)
{
	if (INFO_GTE(FLIST, 2) && show_filelist_progress && (count % 100) == 0)
		emit_filelist_progress(count);
}

static void finish_filelist_progress(const struct file_list *flist)
{
	output_needs_newline = 0;
	if (INFO_GTE(FLIST, 2)) {
		/* This overwrites the progress line */
		rprintf(FINFO, "%d file%sto consider\n",
			flist->used, flist->used == 1 ? " " : "s ");
	} else {
		rprintf(FINFO, "done\n");
	}
}

void show_flist_stats(void)
{
	/* Nothing yet */
}

/* Stat either a symlink or its referent, depending on the settings of
 * copy_links, copy_unsafe_links, etc.  Returns -1 on error, 0 on success.
 *
 * If path is the name of a symlink, then the linkbuf buffer (which must hold
 * MAXPATHLEN chars) will be set to the symlink's target string.
 *
 * The stat structure pointed to by stp will contain information about the
 * link or the referent as appropriate, if they exist. */
static int readlink_stat(const char *path, STRUCT_STAT *stp, char *linkbuf)
{
#ifdef SUPPORT_LINKS
	if (link_stat(path, stp, copy_dirlinks) < 0)
		return -1;
	if (S_ISLNK(stp->st_mode)) {
		int llen = do_readlink(path, linkbuf, MAXPATHLEN - 1);
		if (llen < 0)
			return -1;
		linkbuf[llen] = '\0';
		if (copy_unsafe_links && unsafe_symlink(linkbuf, path)) {
			if (INFO_GTE(SYMSAFE, 1)) {
				rprintf(FINFO,"copying unsafe symlink \"%s\" -> \"%s\"\n",
					path, linkbuf);
			}
			return x_stat(path, stp, NULL);
		}
		if (munge_symlinks && am_sender && llen > SYMLINK_PREFIX_LEN
		 && strncmp(linkbuf, SYMLINK_PREFIX, SYMLINK_PREFIX_LEN) == 0) {
			memmove(linkbuf, linkbuf + SYMLINK_PREFIX_LEN,
				llen - SYMLINK_PREFIX_LEN + 1);
		}
	}
	return 0;
#else
	return x_stat(path, stp, NULL);
#endif
}

int link_stat(const char *path, STRUCT_STAT *stp, int follow_dirlinks)
{
#ifdef SUPPORT_LINKS
	if (copy_links)
		return x_stat(path, stp, NULL);
	if (x_lstat(path, stp, NULL) < 0)
		return -1;
	if (follow_dirlinks && S_ISLNK(stp->st_mode)) {
		STRUCT_STAT st;
		if (x_stat(path, &st, NULL) == 0 && S_ISDIR(st.st_mode))
			*stp = st;
	}
	return 0;
#else
	return x_stat(path, stp, NULL);
#endif
}

static inline int path_is_daemon_excluded(char *path, int ignore_filename)
{
	if (daemon_filter_list.head) {
		char *slash = path;

		while ((slash = strchr(slash+1, '/')) != NULL) {
			int ret;
			*slash = '\0';
			ret = check_filter(&daemon_filter_list, FLOG, path, 1);
			*slash = '/';
			if (ret < 0) {
				errno = ENOENT;
				return 1;
			}
		}

		if (!ignore_filename
		 && check_filter(&daemon_filter_list, FLOG, path, 1) < 0) {
			errno = ENOENT;
			return 1;
		}
	}

	return 0;
}

static inline int is_excluded(const char *fname, int is_dir, int filter_level)
{
	return name_is_excluded(fname, is_dir ? NAME_IS_DIR : NAME_IS_FILE, filter_level);
}

static void send_directory(int f, struct file_list *flist,
			   char *fbuf, int len, int flags);

static const char *pathname, *orig_dir;
static int pathname_len;

/* Make sure flist can hold at least flist->used + extra entries. */
static void flist_expand(struct file_list *flist, int extra)
{
	struct file_struct **new_ptr;

	if (flist->used + extra <= flist->malloced)
		return;

	if (flist->malloced < FLIST_START)
		flist->malloced = FLIST_START;
	else if (flist->malloced >= FLIST_LINEAR)
		flist->malloced += FLIST_LINEAR;
	else if (flist->malloced < FLIST_START_LARGE/16)
		flist->malloced *= 4;
	else
		flist->malloced *= 2;

	/* In case count jumped or we are starting the list
	 * with a known size just set it. */
	if (flist->malloced < flist->used + extra)
		flist->malloced = flist->used + extra;

	new_ptr = realloc_array(flist->files, struct file_struct *, flist->malloced);

	if (DEBUG_GTE(FLIST, 1) && flist->files) {
		rprintf(FCLIENT, "[%s] expand file_list pointer array to %s bytes, did%s move\n",
		    who_am_i(),
		    big_num(sizeof flist->files[0] * flist->malloced),
		    (new_ptr == flist->files) ? " not" : "");
	}

	flist->files = new_ptr;
}

static void flist_done_allocating(struct file_list *flist)
{
	void *ptr = pool_boundary(flist->file_pool, 8*1024);
	if (flist->pool_boundary == ptr)
		flist->pool_boundary = NULL; /* list didn't use any pool memory */
	else
		flist->pool_boundary = ptr;
}

/* Call this with EITHER (1) "file, NULL, 0" to chdir() to the file's
 * F_PATHNAME(), or (2) "NULL, dir, dirlen" to chdir() to the supplied dir,
 * with dir == NULL taken to be the starting directory, and dirlen < 0
 * indicating that strdup(dir) should be called and then the -dirlen length
 * value checked to ensure that it is not daemon-excluded. */
int change_pathname(struct file_struct *file, const char *dir, int dirlen)
{
	if (dirlen < 0) {
		char *cpy = strdup(dir);
		if (*cpy != '/')
			change_dir(orig_dir, CD_SKIP_CHDIR);
		if (path_is_daemon_excluded(cpy, 0))
			goto chdir_error;
		dir = cpy;
		dirlen = -dirlen;
	} else {
		if (file) {
			if (pathname == F_PATHNAME(file))
				return 1;
			dir = F_PATHNAME(file);
			if (dir)
				dirlen = strlen(dir);
		} else if (pathname == dir)
			return 1;
		if (dir && *dir != '/')
			change_dir(orig_dir, CD_SKIP_CHDIR);
	}

	pathname = dir;
	pathname_len = dirlen;

	if (!dir)
		dir = orig_dir;

	if (!change_dir(dir, CD_NORMAL)) {
	  chdir_error:
		io_error |= IOERR_GENERAL;
		rsyserr(FERROR_XFER, errno, "change_dir %s failed", full_fname(dir));
		if (dir != orig_dir)
			change_dir(orig_dir, CD_NORMAL);
		pathname = NULL;
		pathname_len = 0;
		return 0;
	}

	return 1;
}

static void send_file_entry(int f, const char *fname, struct file_struct *file,
#ifdef SUPPORT_LINKS
			    const char *symlink_name, int symlink_len,
#endif
			    int ndx, int first_ndx)
{
	static time_t modtime, atime;
#ifdef SUPPORT_CRTIMES
	static time_t crtime;
#endif
	static mode_t mode;
#ifdef SUPPORT_HARD_LINKS
	static int64 dev;
#endif
	static dev_t rdev;
	static uint32 rdev_major;
	static uid_t uid;
	static gid_t gid;
	static const char *user_name, *group_name;
	static char lastname[MAXPATHLEN];
#ifdef SUPPORT_HARD_LINKS
	int first_hlink_ndx = -1;
#endif
	int l1, l2;
	int xflags;

	/* Initialize starting value of xflags and adjust counts. */
	if (S_ISREG(file->mode))
		xflags = 0;
	else if (S_ISDIR(file->mode)) {
		stats.num_dirs++;
		if (protocol_version >= 30) {
			if (file->flags & FLAG_CONTENT_DIR)
				xflags = file->flags & FLAG_TOP_DIR;
			else if (file->flags & FLAG_IMPLIED_DIR)
				xflags = XMIT_TOP_DIR | XMIT_NO_CONTENT_DIR;
			else
				xflags = XMIT_NO_CONTENT_DIR;
		} else
			xflags = file->flags & FLAG_TOP_DIR; /* FLAG_TOP_DIR == XMIT_TOP_DIR */
	} else {
		if (S_ISLNK(file->mode))
			stats.num_symlinks++;
		else if (IS_DEVICE(file->mode))
			stats.num_devices++;
		else if (IS_SPECIAL(file->mode))
			stats.num_specials++;
		xflags = 0;
	}

	if (file->mode == mode)
		xflags |= XMIT_SAME_MODE;
	else
		mode = file->mode;

	if (preserve_devices && IS_DEVICE(mode)) {
		if (protocol_version < 28) {
			if (tmp_rdev == rdev)
				xflags |= XMIT_SAME_RDEV_pre28;
			else
				rdev = tmp_rdev;
		} else {
			rdev = tmp_rdev;
			if ((uint32)major(rdev) == rdev_major)
				xflags |= XMIT_SAME_RDEV_MAJOR;
			else
				rdev_major = major(rdev);
			if (protocol_version < 30 && (uint32)minor(rdev) <= 0xFFu)
				xflags |= XMIT_RDEV_MINOR_8_pre30;
		}
	} else if (preserve_specials && IS_SPECIAL(mode) && protocol_version < 31) {
		/* Special files don't need an rdev number, so just make
		 * the historical transmission of the value efficient. */
		if (protocol_version < 28)
			xflags |= XMIT_SAME_RDEV_pre28;
		else {
			rdev = MAKEDEV(rdev_major, 0);
			xflags |= XMIT_SAME_RDEV_MAJOR;
			if (protocol_version < 30)
				xflags |= XMIT_RDEV_MINOR_8_pre30;
		}
	} else if (protocol_version < 28)
		rdev = MAKEDEV(0, 0);
	if (!preserve_uid || ((uid_t)F_OWNER(file) == uid && *lastname))
		xflags |= XMIT_SAME_UID;
	else {
		uid = F_OWNER(file);
		if (!numeric_ids) {
			user_name = add_uid(uid);
			if (inc_recurse && user_name)
				xflags |= XMIT_USER_NAME_FOLLOWS;
		}
	}
	if (!preserve_gid || ((gid_t)F_GROUP(file) == gid && *lastname))
		xflags |= XMIT_SAME_GID;
	else {
		gid = F_GROUP(file);
		if (!numeric_ids) {
			group_name = add_gid(gid);
			if (inc_recurse && group_name)
				xflags |= XMIT_GROUP_NAME_FOLLOWS;
		}
	}
	if (file->modtime == modtime)
		xflags |= XMIT_SAME_TIME;
	else
		modtime = file->modtime;
	if (NSEC_BUMP(file) && protocol_version >= 31)
		xflags |= XMIT_MOD_NSEC;
	if (atimes_ndx && !S_ISDIR(mode)) {
		if (F_ATIME(file) == atime)
			xflags |= XMIT_SAME_ATIME;
		else
			atime = F_ATIME(file);
	}
#ifdef SUPPORT_CRTIMES
	if (crtimes_ndx) {
		crtime = F_CRTIME(file);
		if (crtime == modtime)
			xflags |= XMIT_CRTIME_EQ_MTIME;
	}
#endif

#ifdef SUPPORT_HARD_LINKS
	if (tmp_dev != -1) {
		if (protocol_version >= 30) {
			struct ht_int64_node *np = idev_find(tmp_dev, tmp_ino);
			first_hlink_ndx = (int32)(long)np->data; /* is -1 when new */
			if (first_hlink_ndx < 0) {
				np->data = (void*)(long)(first_ndx + ndx);
				xflags |= XMIT_HLINK_FIRST;
			}
			if (DEBUG_GTE(HLINK, 1)) {
				if (first_hlink_ndx >= 0) {
					rprintf(FINFO, "[%s] #%d hard-links #%d (%sabbrev)\n",
						who_am_i(), first_ndx + ndx, first_hlink_ndx,
						first_hlink_ndx >= first_ndx ? "" : "un");
				} else if (DEBUG_GTE(HLINK, 3)) {
					rprintf(FINFO, "[%s] dev:inode for #%d is %s:%s\n",
						who_am_i(), first_ndx + ndx,
						big_num(tmp_dev), big_num(tmp_ino));
				}
			}
		} else {
			if (tmp_dev == dev) {
				if (protocol_version >= 28)
					xflags |= XMIT_SAME_DEV_pre30;
			} else
				dev = tmp_dev;
		}
		xflags |= XMIT_HLINKED;
	}
#endif

	for (l1 = 0;
	    lastname[l1] && (fname[l1] == lastname[l1]) && (l1 < 255);
	    l1++) {}
	l2 = strlen(fname+l1);

	if (l1 > 0)
		xflags |= XMIT_SAME_NAME;
	if (l2 > 255)
		xflags |= XMIT_LONG_NAME;

	/* We must avoid sending a flag value of 0 (or an initial byte of
	 * 0 for the older xflags protocol) or it will signal the end of
	 * the list.  Note that the use of XMIT_TOP_DIR on a non-dir has
	 * no meaning, so it's a harmless way to add a bit to the first
	 * flag byte. */
	if (xfer_flags_as_varint)
		write_varint(f, xflags ? xflags : XMIT_EXTENDED_FLAGS);
	else if (protocol_version >= 28) {
		if (!xflags && !S_ISDIR(mode))
			xflags |= XMIT_TOP_DIR;
		if ((xflags & 0xFF00) || !xflags) {
			xflags |= XMIT_EXTENDED_FLAGS;
			write_shortint(f, xflags);
		} else
			write_byte(f, xflags);
	} else {
		if (!(xflags & 0xFF))
			xflags |= S_ISDIR(mode) ? XMIT_LONG_NAME : XMIT_TOP_DIR;
		write_byte(f, xflags);
	}
	if (xflags & XMIT_SAME_NAME)
		write_byte(f, l1);
	if (xflags & XMIT_LONG_NAME)
		write_varint30(f, l2);
	else
		write_byte(f, l2);
	write_buf(f, fname + l1, l2);

#ifdef SUPPORT_HARD_LINKS
	if (first_hlink_ndx >= 0) {
		write_varint(f, first_hlink_ndx);
		if (first_hlink_ndx >= first_ndx)
			goto the_end;
	}
#endif

	write_varlong30(f, F_LENGTH(file), 3);
	if (!(xflags & XMIT_SAME_TIME)) {
		if (protocol_version >= 30)
			write_varlong(f, modtime, 4);
		else
			write_int(f, modtime);
	}
	if (xflags & XMIT_MOD_NSEC)
		write_varint(f, F_MOD_NSEC(file));
#ifdef SUPPORT_CRTIMES
	if (crtimes_ndx && !(xflags & XMIT_CRTIME_EQ_MTIME))
		write_varlong(f, crtime, 4);
#endif
	if (!(xflags & XMIT_SAME_MODE))
		write_int(f, to_wire_mode(mode));
	if (atimes_ndx && !S_ISDIR(mode) && !(xflags & XMIT_SAME_ATIME))
		write_varlong(f, atime, 4);
	if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
		if (protocol_version < 30)
			write_int(f, uid);
		else {
			write_varint(f, uid);
			if (xflags & XMIT_USER_NAME_FOLLOWS) {
				int len = strlen(user_name);
				write_byte(f, len);
				write_buf(f, user_name, len);
			}
		}
	}
	if (preserve_gid && !(xflags & XMIT_SAME_GID)) {
		if (protocol_version < 30)
			write_int(f, gid);
		else {
			write_varint(f, gid);
			if (xflags & XMIT_GROUP_NAME_FOLLOWS) {
				int len = strlen(group_name);
				write_byte(f, len);
				write_buf(f, group_name, len);
			}
		}
	}
	if ((preserve_devices && IS_DEVICE(mode))
	 || (preserve_specials && IS_SPECIAL(mode) && protocol_version < 31)) {
		if (protocol_version < 28) {
			if (!(xflags & XMIT_SAME_RDEV_pre28))
				write_int(f, (int)rdev);
		} else {
			if (!(xflags & XMIT_SAME_RDEV_MAJOR))
				write_varint30(f, major(rdev));
			if (protocol_version >= 30)
				write_varint(f, minor(rdev));
			else if (xflags & XMIT_RDEV_MINOR_8_pre30)
				write_byte(f, minor(rdev));
			else
				write_int(f, minor(rdev));
		}
	}

#ifdef SUPPORT_LINKS
	if (symlink_len) {
		write_varint30(f, symlink_len);
		write_buf(f, symlink_name, symlink_len);
	}
#endif

#ifdef SUPPORT_HARD_LINKS
	if (tmp_dev != -1 && protocol_version < 30) {
		/* Older protocols expect the dev number to be transmitted
		 * 1-incremented so that it is never zero. */
		if (protocol_version < 26) {
			/* 32-bit dev_t and ino_t */
			write_int(f, (int32)(dev+1));
			write_int(f, (int32)tmp_ino);
		} else {
			/* 64-bit dev_t and ino_t */
			if (!(xflags & XMIT_SAME_DEV_pre30))
				write_longint(f, dev+1);
			write_longint(f, tmp_ino);
		}
	}
#endif

	if (always_checksum && (S_ISREG(mode) || protocol_version < 28)) {
		const char *sum;
		if (S_ISREG(mode))
			sum = tmp_sum;
		else {
			/* Prior to 28, we sent a useless set of nulls. */
			sum = empty_sum;
		}
		write_buf(f, sum, flist_csum_len);
	}

#ifdef SUPPORT_HARD_LINKS
  the_end:
#endif
	strlcpy(lastname, fname, MAXPATHLEN);

	if (S_ISREG(mode) || S_ISLNK(mode))
		stats.total_size += F_LENGTH(file);
}

static struct file_struct *recv_file_entry(int f, struct file_list *flist, int xflags)
{
	static int64 modtime, atime;
#ifdef SUPPORT_CRTIMES
	static time_t crtime;
#endif
	static mode_t mode;
#ifdef SUPPORT_HARD_LINKS
	static int64 dev;
#endif
	static dev_t rdev;
	static uint32 rdev_major;
	static uid_t uid;
	static gid_t gid;
	static uint16 gid_flags;
	static char lastname[MAXPATHLEN], *lastdir;
	static int lastdir_depth, lastdir_len = -1;
	static unsigned int del_hier_name_len = 0;
	static int in_del_hier = 0;
	char thisname[MAXPATHLEN];
	unsigned int l1 = 0, l2 = 0;
	int alloc_len, basename_len, linkname_len;
	int extra_len = file_extra_cnt * EXTRA_LEN;
	int first_hlink_ndx = -1;
	char real_ISREG_entry;
	int64 file_length;
#ifdef CAN_SET_NSEC
	uint32 modtime_nsec;
#endif
	const char *basename;
	struct file_struct *file;
	alloc_pool_t *pool;
	char *bp;

	if (xflags & XMIT_SAME_NAME)
		l1 = read_byte(f);

	if (xflags & XMIT_LONG_NAME)
		l2 = read_varint30(f);
	else
		l2 = read_byte(f);

	if (l2 >= MAXPATHLEN - l1) {
		rprintf(FERROR,
			"overflow: xflags=0x%x l1=%d l2=%d lastname=%s [%s]\n",
			xflags, l1, l2, lastname, who_am_i());
		overflow_exit("recv_file_entry");
	}

	strlcpy(thisname, lastname, l1 + 1);
	read_sbuf(f, &thisname[l1], l2);
	thisname[l1 + l2] = 0;

	/* Abuse basename_len for a moment... */
	basename_len = strlcpy(lastname, thisname, MAXPATHLEN);

#ifdef ICONV_OPTION
	if (ic_recv != (iconv_t)-1) {
		xbuf outbuf, inbuf;

		INIT_CONST_XBUF(outbuf, thisname);
		INIT_XBUF(inbuf, lastname, basename_len, (size_t)-1);

		if (iconvbufs(ic_recv, &inbuf, &outbuf, ICB_INIT) < 0) {
			io_error |= IOERR_GENERAL;
			rprintf(FERROR_UTF8,
			    "[%s] cannot convert filename: %s (%s)\n",
			    who_am_i(), lastname, strerror(errno));
			outbuf.len = 0;
		}
		thisname[outbuf.len] = '\0';
	}
#endif

	if (*thisname
	 && (clean_fname(thisname, CFN_REFUSE_DOT_DOT_DIRS) < 0 || (!relative_paths && *thisname == '/'))) {
		rprintf(FERROR, "ABORTING due to unsafe pathname from sender: %s\n", thisname);
		exit_cleanup(RERR_UNSUPPORTED);
	}

	if (sanitize_paths)
		sanitize_path(thisname, thisname, "", 0, SP_DEFAULT);

	if ((basename = strrchr(thisname, '/')) != NULL) {
		int len = basename++ - thisname;
		if (len != lastdir_len || memcmp(thisname, lastdir, len) != 0) {
			lastdir = new_array(char, len + 1);
			memcpy(lastdir, thisname, len);
			lastdir[len] = '\0';
			lastdir_len = len;
			lastdir_depth = count_dir_elements(lastdir);
		}
	} else
		basename = thisname;
	basename_len = strlen(basename) + 1; /* count the '\0' */

#ifdef SUPPORT_HARD_LINKS
	if (protocol_version >= 30
	 && BITS_SETnUNSET(xflags, XMIT_HLINKED, XMIT_HLINK_FIRST)) {
		first_hlink_ndx = read_varint(f);
		if (first_hlink_ndx < 0 || first_hlink_ndx >= flist->ndx_start + flist->used) {
			rprintf(FERROR,
				"hard-link reference out of range: %d (%d)\n",
				first_hlink_ndx, flist->ndx_start + flist->used);
			exit_cleanup(RERR_PROTOCOL);
		}
		if (DEBUG_GTE(HLINK, 1)) {
			rprintf(FINFO, "[%s] #%d hard-links #%d (%sabbrev)\n",
				who_am_i(), flist->used+flist->ndx_start, first_hlink_ndx,
				first_hlink_ndx >= flist->ndx_start ? "" : "un");
		}
		if (first_hlink_ndx >= flist->ndx_start) {
			struct file_struct *first = flist->files[first_hlink_ndx - flist->ndx_start];
			file_length = F_LENGTH(first);
			modtime = first->modtime;
#ifdef CAN_SET_NSEC
			modtime_nsec = F_MOD_NSEC_or_0(first);
#endif
			mode = first->mode;
			if (atimes_ndx && !S_ISDIR(mode))
				atime = F_ATIME(first);
#ifdef SUPPORT_CRTIMES
			if (crtimes_ndx)
				crtime = F_CRTIME(first);
#endif
			if (preserve_uid)
				uid = F_OWNER(first);
			if (preserve_gid)
				gid = F_GROUP(first);
			if (preserve_devices && IS_DEVICE(mode)) {
				uint32 *devp = F_RDEV_P(first);
				rdev_major = DEV_MAJOR(devp);
				rdev = MAKEDEV(rdev_major, DEV_MINOR(devp));
				extra_len += DEV_EXTRA_CNT * EXTRA_LEN;
			}
			if (preserve_links && S_ISLNK(mode))
				linkname_len = strlen(F_SYMLINK(first)) + 1;
			else
				linkname_len = 0;
			real_ISREG_entry = S_ISREG(mode) ? 1 : 0;
			goto create_object;
		}
	}
#endif

	file_length = read_varlong30(f, 3);
	if (!(xflags & XMIT_SAME_TIME)) {
		if (protocol_version >= 30) {
			modtime = read_varlong(f, 4);
#if SIZEOF_TIME_T < SIZEOF_INT64
			if (!am_generator && (int64)(time_t)modtime != modtime) {
				rprintf(FERROR_XFER,
				    "Time value of %s truncated on receiver.\n",
				    lastname);
			}
#endif
		} else
			modtime = read_uint(f);
	}
	if (xflags & XMIT_MOD_NSEC)
#ifndef CAN_SET_NSEC
		(void)read_varint(f);
#else
		modtime_nsec = read_varint(f);
	else
		modtime_nsec = 0;
#endif
#ifdef SUPPORT_CRTIMES
	if (crtimes_ndx) {
		if (xflags & XMIT_CRTIME_EQ_MTIME)
			crtime = modtime;
		else
			crtime = read_varlong(f, 4);
#if SIZEOF_TIME_T < SIZEOF_INT64
		if (!am_generator && (int64)(time_t)crtime != crtime) {
			rprintf(FERROR_XFER,
				"Create time value of %s truncated on receiver.\n",
				lastname);
		}
#endif
	}
#endif
	if (!(xflags & XMIT_SAME_MODE))
		mode = from_wire_mode(read_int(f));
	if (atimes_ndx && !S_ISDIR(mode) && !(xflags & XMIT_SAME_ATIME)) {
		atime = read_varlong(f, 4);
#if SIZEOF_TIME_T < SIZEOF_INT64
		if (!am_generator && (int64)(time_t)atime != atime) {
			rprintf(FERROR_XFER,
				"Access time value of %s truncated on receiver.\n",
				lastname);
		}
#endif
	}

	if (chmod_modes && !S_ISLNK(mode) && mode)
		mode = tweak_mode(mode, chmod_modes);

	if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
		if (protocol_version < 30)
			uid = (uid_t)read_int(f);
		else {
			uid = (uid_t)read_varint(f);
			if (xflags & XMIT_USER_NAME_FOLLOWS)
				uid = recv_user_name(f, uid);
			else if (inc_recurse && am_root && (!numeric_ids || usermap))
				uid = match_uid(uid);
		}
	}
	if (preserve_gid && !(xflags & XMIT_SAME_GID)) {
		if (protocol_version < 30)
			gid = (gid_t)read_int(f);
		else {
			gid = (gid_t)read_varint(f);
			gid_flags = 0;
			if (xflags & XMIT_GROUP_NAME_FOLLOWS)
				gid = recv_group_name(f, gid, &gid_flags);
			else if (inc_recurse && (!am_root || !numeric_ids || groupmap))
				gid = match_gid(gid, &gid_flags);
		}
	}

	if ((preserve_devices && IS_DEVICE(mode))
	 || (preserve_specials && IS_SPECIAL(mode) && protocol_version < 31)) {
		if (protocol_version < 28) {
			if (!(xflags & XMIT_SAME_RDEV_pre28))
				rdev = (dev_t)read_int(f);
		} else {
			uint32 rdev_minor;
			if (!(xflags & XMIT_SAME_RDEV_MAJOR))
				rdev_major = read_varint30(f);
			if (protocol_version >= 30)
				rdev_minor = read_varint(f);
			else if (xflags & XMIT_RDEV_MINOR_8_pre30)
				rdev_minor = read_byte(f);
			else
				rdev_minor = read_int(f);
			rdev = MAKEDEV(rdev_major, rdev_minor);
		}
		if (IS_DEVICE(mode))
			extra_len += DEV_EXTRA_CNT * EXTRA_LEN;
		file_length = 0;
	} else if (protocol_version < 28)
		rdev = MAKEDEV(0, 0);

#ifdef SUPPORT_LINKS
	if (preserve_links && S_ISLNK(mode)) {
		linkname_len = read_varint30(f) + 1; /* count the '\0' */
		if (linkname_len <= 0 || linkname_len > MAXPATHLEN) {
			rprintf(FERROR, "overflow: linkname_len=%d\n",
				linkname_len - 1);
			overflow_exit("recv_file_entry");
		}
#ifdef ICONV_OPTION
		/* We don't know how much extra room we need to convert
		 * the as-yet-unread symlink data, so let's hope that a
		 * double-size buffer is plenty. */
		if (sender_symlink_iconv)
			linkname_len *= 2;
#endif
		if (munge_symlinks)
			linkname_len += SYMLINK_PREFIX_LEN;
	}
	else
#endif
		linkname_len = 0;

	if (copy_devices && IS_DEVICE(mode)) {
		/* This is impossible in the official release, but some pre-release patches
		 * didn't convert the device into a file before sending, so we'll do it here
		 * (even though the length is typically 0 and any checksum data is zeros). */
		mode = S_IFREG | (mode & ACCESSPERMS);
		modtime = time(NULL); /* The mtime on the device is not up-to-date, so set it to "now". */
		real_ISREG_entry = 0;
	} else
		real_ISREG_entry = S_ISREG(mode) ? 1 : 0;

#ifdef SUPPORT_HARD_LINKS
  create_object:
	if (preserve_hard_links) {
		if (protocol_version < 28 && real_ISREG_entry)
			xflags |= XMIT_HLINKED;
		if (xflags & XMIT_HLINKED)
			extra_len += (inc_recurse+1) * EXTRA_LEN;
	}
#endif

#ifdef SUPPORT_ACLS
	/* Directories need an extra int32 for the default ACL. */
	if (preserve_acls && S_ISDIR(mode))
		extra_len += EXTRA_LEN;
#endif

	if (always_checksum && S_ISREG(mode))
		extra_len += SUM_EXTRA_CNT * EXTRA_LEN;

#if SIZEOF_INT64 >= 8
	if (file_length > 0xFFFFFFFFu && S_ISREG(mode))
		extra_len += EXTRA_LEN;
#endif
#ifdef CAN_SET_NSEC
	if (modtime_nsec)
		extra_len += EXTRA_LEN;
#endif
	if (file_length < 0) {
		rprintf(FERROR, "Offset underflow: file-length is negative\n");
		exit_cleanup(RERR_UNSUPPORTED);
	}

	if (*thisname == '/' ? thisname[1] != '.' || thisname[2] != '\0' : *thisname != '.' || thisname[1] != '\0') {
		int filt_flags = S_ISDIR(mode) ? NAME_IS_DIR : NAME_IS_FILE;
		if (!trust_sender_filter /* a per-dir filter rule means we must trust the sender's filtering */
		 && filter_list.head && check_server_filter(&filter_list, FINFO, thisname, filt_flags) < 0) {
			rprintf(FERROR, "ERROR: rejecting excluded file-list name: %s\n", thisname);
			exit_cleanup(RERR_UNSUPPORTED);
		}
		if (implied_filter_list.head && check_filter(&implied_filter_list, FINFO, thisname, filt_flags) <= 0) {
			rprintf(FERROR, "ERROR: rejecting unrequested file-list name: %s\n", thisname);
			exit_cleanup(RERR_UNSUPPORTED);
		}
	}

	if (inc_recurse && S_ISDIR(mode)) {
		if (one_file_system) {
			/* Room to save the dir's device for -x */
			extra_len += DEV_EXTRA_CNT * EXTRA_LEN;
		}
		pool = dir_flist->file_pool;
	} else
		pool = flist->file_pool;

#if EXTRA_ROUNDING > 0
	if (extra_len & (EXTRA_ROUNDING * EXTRA_LEN))
		extra_len = (extra_len | (EXTRA_ROUNDING * EXTRA_LEN)) + EXTRA_LEN;
#endif

	alloc_len = FILE_STRUCT_LEN + extra_len + basename_len
		  + linkname_len;
	bp = pool_alloc(pool, alloc_len, "recv_file_entry");

	memset(bp, 0, extra_len + FILE_STRUCT_LEN);
	bp += extra_len;
	file = (struct file_struct *)bp;
	bp += FILE_STRUCT_LEN;

	memcpy(bp, basename, basename_len);

#ifdef SUPPORT_HARD_LINKS
	if (xflags & XMIT_HLINKED
#ifndef CAN_HARDLINK_SYMLINK
	 && !S_ISLNK(mode)
#endif
#ifndef CAN_HARDLINK_SPECIAL
	 && !IS_SPECIAL(mode) && !IS_DEVICE(mode)
#endif
	)
		file->flags |= FLAG_HLINKED;
#endif
	file->modtime = (time_t)modtime;
#ifdef CAN_SET_NSEC
	if (modtime_nsec) {
		file->flags |= FLAG_MOD_NSEC;
		F_MOD_NSEC(file) = modtime_nsec;
	}
#endif
	file->len32 = (uint32)file_length;
#if SIZEOF_INT64 >= 8
	if (file_length > 0xFFFFFFFFu && S_ISREG(mode)) {
#if SIZEOF_CAPITAL_OFF_T < 8
		rprintf(FERROR, "Offset overflow: attempted 64-bit file-length\n");
		exit_cleanup(RERR_UNSUPPORTED);
#else
		file->flags |= FLAG_LENGTH64;
		F_HIGH_LEN(file) = (uint32)(file_length >> 32);
#endif
	}
#endif
	file->mode = mode;
	if (preserve_uid)
		F_OWNER(file) = uid;
	if (preserve_gid) {
		F_GROUP(file) = gid;
		file->flags |= gid_flags;
	}
	if (atimes_ndx && !S_ISDIR(mode))
		F_ATIME(file) = atime;
#ifdef SUPPORT_CRTIMES
	if (crtimes_ndx)
		F_CRTIME(file) = crtime;
#endif
	if (unsort_ndx)
		F_NDX(file) = flist->used + flist->ndx_start;

	if (basename != thisname) {
		file->dirname = lastdir;
		F_DEPTH(file) = lastdir_depth + 1;
	} else
		F_DEPTH(file) = 1;

	if (S_ISDIR(mode)) {
		if (basename_len == 1+1 && *basename == '.') /* +1 for '\0' */
			F_DEPTH(file)--;
		if (protocol_version >= 30) {
			if (!(xflags & XMIT_NO_CONTENT_DIR)) {
				if (xflags & XMIT_TOP_DIR)
					file->flags |= FLAG_TOP_DIR;
				file->flags |= FLAG_CONTENT_DIR;
			} else if (xflags & XMIT_TOP_DIR)
				file->flags |= FLAG_IMPLIED_DIR;
		} else if (xflags & XMIT_TOP_DIR) {
			in_del_hier = recurse;
			del_hier_name_len = F_DEPTH(file) == 0 ? 0 : l1 + l2;
			if (relative_paths && del_hier_name_len > 2
			    && lastname[del_hier_name_len-1] == '.'
			    && lastname[del_hier_name_len-2] == '/')
				del_hier_name_len -= 2;
			file->flags |= FLAG_TOP_DIR | FLAG_CONTENT_DIR;
		} else if (in_del_hier) {
			if (!relative_paths || !del_hier_name_len
			 || (l1 >= del_hier_name_len
			  && lastname[del_hier_name_len] == '/'))
				file->flags |= FLAG_CONTENT_DIR;
			else
				in_del_hier = 0;
		}
	}

	if (preserve_devices && IS_DEVICE(mode)) {
		uint32 *devp = F_RDEV_P(file);
		DEV_MAJOR(devp) = major(rdev);
		DEV_MINOR(devp) = minor(rdev);
	}

#ifdef SUPPORT_LINKS
	if (linkname_len) {
		bp += basename_len;
		if (first_hlink_ndx >= flist->ndx_start) {
			struct file_struct *first = flist->files[first_hlink_ndx - flist->ndx_start];
			memcpy(bp, F_SYMLINK(first), linkname_len);
		} else {
			if (munge_symlinks) {
				strlcpy(bp, SYMLINK_PREFIX, linkname_len);
				bp += SYMLINK_PREFIX_LEN;
				linkname_len -= SYMLINK_PREFIX_LEN;
			}
#ifdef ICONV_OPTION
			if (sender_symlink_iconv) {
				xbuf outbuf, inbuf;

				alloc_len = linkname_len;
				linkname_len /= 2;

				/* Read the symlink data into the end of our double-sized
				 * buffer and then convert it into the right spot. */
				INIT_XBUF(inbuf, bp + alloc_len - linkname_len,
					  linkname_len - 1, (size_t)-1);
				read_sbuf(f, inbuf.buf, inbuf.len);
				INIT_XBUF(outbuf, bp, 0, alloc_len);

				if (iconvbufs(ic_recv, &inbuf, &outbuf, ICB_INIT) < 0) {
					io_error |= IOERR_GENERAL;
					rprintf(FERROR_XFER,
					    "[%s] cannot convert symlink data for: %s (%s)\n",
					    who_am_i(), full_fname(thisname), strerror(errno));
					bp = (char*)file->basename;
					*bp++ = '\0';
					outbuf.len = 0;
				}
				bp[outbuf.len] = '\0';
			} else
#endif
				read_sbuf(f, bp, linkname_len - 1);
			if (sanitize_paths && !munge_symlinks && *bp)
				sanitize_path(bp, bp, "", lastdir_depth, SP_DEFAULT);
		}
	}
#endif

#ifdef SUPPORT_HARD_LINKS
	if (preserve_hard_links && xflags & XMIT_HLINKED) {
		if (protocol_version >= 30) {
			if (xflags & XMIT_HLINK_FIRST) {
				F_HL_GNUM(file) = flist->ndx_start + flist->used;
			} else
				F_HL_GNUM(file) = first_hlink_ndx;
		} else {
			static int32 cnt = 0;
			struct ht_int64_node *np;
			int64 ino;
			int32 ndx;
			if (protocol_version < 26) {
				dev = read_int(f);
				ino = read_int(f);
			} else {
				if (!(xflags & XMIT_SAME_DEV_pre30))
					dev = read_longint(f);
				ino = read_longint(f);
			}
			np = idev_find(dev, ino);
			ndx = (int32)(long)np->data; /* is -1 when new */
			if (ndx < 0) {
				np->data = (void*)(long)cnt;
				ndx = cnt++;
			}
			F_HL_GNUM(file) = ndx;
		}
	}
#endif

	if (always_checksum && (real_ISREG_entry || protocol_version < 28)) {
		if (real_ISREG_entry)
			bp = F_SUM(file);
		else {
			/* Prior to 28, we get a useless set of nulls. */
			bp = tmp_sum;
		}
		if (first_hlink_ndx >= flist->ndx_start) {
			struct file_struct *first = flist->files[first_hlink_ndx - flist->ndx_start];
			memcpy(bp, F_SUM(first), flist_csum_len);
		} else
			read_buf(f, bp, flist_csum_len);
	}

#ifdef SUPPORT_ACLS
	if (preserve_acls && !S_ISLNK(mode))
		receive_acl(f, file);
#endif
#ifdef SUPPORT_XATTRS
	if (preserve_xattrs)
		receive_xattr(f, file);
#endif

	if (S_ISREG(mode) || S_ISLNK(mode))
		stats.total_size += file_length;

	return file;
}

/* Create a file_struct for a named file by reading its stat() information
 * and performing extensive checks against global options.
 *
 * Returns a pointer to the new file struct, or NULL if there was an error
 * or this file should be excluded.
 *
 * Note: Any error (here or in send_file_name) that results in the omission of
 * an existent source file from the file list should set
 * "io_error |= IOERR_GENERAL" to avoid deletion of the file from the
 * destination if --delete is on. */
struct file_struct *make_file(const char *fname, struct file_list *flist,
			      STRUCT_STAT *stp, int flags, int filter_level)
{
	static char *lastdir;
	static int lastdir_len = -1;
	struct file_struct *file;
	char thisname[MAXPATHLEN];
	char linkname[MAXPATHLEN];
	int alloc_len, basename_len, linkname_len;
	int extra_len = file_extra_cnt * EXTRA_LEN;
	const char *basename;
	alloc_pool_t *pool;
	STRUCT_STAT st;
	char *bp;

	if (strlcpy(thisname, fname, sizeof thisname) >= sizeof thisname) {
		io_error |= IOERR_GENERAL;
		rprintf(FERROR_XFER, "skipping overly long name: %s\n", fname);
		return NULL;
	}
	clean_fname(thisname, 0);
	if (sanitize_paths)
		sanitize_path(thisname, thisname, "", 0, SP_DEFAULT);

	if (stp && (S_ISDIR(stp->st_mode) || IS_MISSING_FILE(*stp))) {
		/* This is needed to handle a "symlink/." with a --relative
		 * dir, or a request to delete a specific file. */
		st = *stp;
		*linkname = '\0'; /* make IBM code checker happy */
	} else if (readlink_stat(thisname, &st, linkname) != 0) {
		int save_errno = errno;
		/* See if file is excluded before reporting an error. */
		if (filter_level != NO_FILTERS
		 && (is_excluded(thisname, 0, filter_level)
		  || is_excluded(thisname, 1, filter_level))) {
			if (ignore_perishable && save_errno != ENOENT)
				non_perishable_cnt++;
			return NULL;
		}
		if (save_errno == ENOENT) {
#ifdef SUPPORT_LINKS
			/* When our options tell us to follow a symlink that
			 * points nowhere, tell the user about the symlink
			 * instead of giving a "vanished" message.  We only
			 * dereference a symlink if one of the --copy*links
			 * options was specified, so there's no need for the
			 * extra lstat() if one of these options isn't on. */
			if ((copy_links || copy_unsafe_links || copy_dirlinks)
			 && x_lstat(thisname, &st, NULL) == 0
			 && S_ISLNK(st.st_mode)) {
				io_error |= IOERR_GENERAL;
				rprintf(FERROR_XFER, "symlink has no referent: %s\n",
					full_fname(thisname));
			} else
#endif
			{
				enum logcode c = am_daemon && protocol_version < 28
					       ? FERROR : FWARNING;
				io_error |= IOERR_VANISHED;
				rprintf(c, "file has vanished: %s\n",
					full_fname(thisname));
			}
		} else {
			io_error |= IOERR_GENERAL;
			rsyserr(FERROR_XFER, save_errno, "readlink_stat(%s) failed",
				full_fname(thisname));
		}
		return NULL;
	} else if (IS_MISSING_FILE(st)) {
		io_error |= IOERR_GENERAL;
		rprintf(FINFO, "skipping file with bogus (zero) st_mode: %s\n",
			full_fname(thisname));
		return NULL;
	}

	if (filter_level == NO_FILTERS)
		goto skip_filters;

	if (S_ISDIR(st.st_mode)) {
		if (!xfer_dirs) {
			rprintf(FINFO, "skipping directory %s\n", thisname);
			return NULL;
		}
		/* -x only affects dirs because we need to avoid recursing
		 * into a mount-point directory, not to avoid copying a
		 * symlinked file if -L (or similar) was specified. */
		if (one_file_system && st.st_dev != filesystem_dev
		 && BITS_SETnUNSET(flags, FLAG_CONTENT_DIR, FLAG_TOP_DIR)) {
			if (one_file_system > 1) {
				if (INFO_GTE(MOUNT, 1)) {
					rprintf(FINFO,
					    "[%s] skipping mount-point dir %s\n",
					    who_am_i(), thisname);
				}
				return NULL;
			}
			flags |= FLAG_MOUNT_DIR;
			flags &= ~FLAG_CONTENT_DIR;
		}
	} else
		flags &= ~FLAG_CONTENT_DIR;

	if (is_excluded(thisname, S_ISDIR(st.st_mode) != 0, filter_level)) {
		if (ignore_perishable)
			non_perishable_cnt++;
		return NULL;
	}

	if (lp_ignore_nonreadable(module_id)) {
#ifdef SUPPORT_LINKS
		if (!S_ISLNK(st.st_mode))
#endif
			if (access(thisname, R_OK) != 0)
				return NULL;
	}

  skip_filters:

	/* Only divert a directory in the main transfer. */
	if (flist) {
		if (flist->prev && S_ISDIR(st.st_mode)
		 && flags & FLAG_DIVERT_DIRS) {
			/* Room for parent/sibling/next-child info. */
			extra_len += DIRNODE_EXTRA_CNT * EXTRA_LEN;
			if (relative_paths)
				extra_len += PTR_EXTRA_CNT * EXTRA_LEN;
			pool = dir_flist->file_pool;
		} else
			pool = flist->file_pool;
	} else {
#ifdef SUPPORT_ACLS
		/* Directories need an extra int32 for the default ACL. */
		if (preserve_acls && S_ISDIR(st.st_mode))
			extra_len += EXTRA_LEN;
#endif
		pool = NULL;
	}

	if (DEBUG_GTE(FLIST, 2)) {
		rprintf(FINFO, "[%s] make_file(%s,*,%d)\n",
			who_am_i(), thisname, filter_level);
	}

	if ((basename = strrchr(thisname, '/')) != NULL) {
		int len = basename++ - thisname;
		if (len != lastdir_len || memcmp(thisname, lastdir, len) != 0) {
			lastdir = new_array(char, len + 1);
			memcpy(lastdir, thisname, len);
			lastdir[len] = '\0';
			lastdir_len = len;
		}
	} else
		basename = thisname;
	basename_len = strlen(basename) + 1; /* count the '\0' */

#ifdef SUPPORT_LINKS
	linkname_len = S_ISLNK(st.st_mode) ? strlen(linkname) + 1 : 0;
#else
	linkname_len = 0;
#endif

	if (copy_devices && am_sender && IS_DEVICE(st.st_mode)) {
		if (st.st_size == 0) {
			int fd = do_open_checklinks(fname);
			if (fd >= 0) {
				st.st_size = get_device_size(fd, fname);
				close(fd);
			}
		}
		st.st_mode = S_IFREG | (st.st_mode & ACCESSPERMS);
		st.st_mtime = time(NULL); /* The mtime on the device is not up-to-date, so set it to "now". */
	}

#ifdef ST_MTIME_NSEC
	if (st.ST_MTIME_NSEC && protocol_version >= 31)
		extra_len += EXTRA_LEN;
#endif
#if SIZEOF_CAPITAL_OFF_T >= 8
	if (st.st_size > 0xFFFFFFFFu && S_ISREG(st.st_mode))
		extra_len += EXTRA_LEN;
#endif

	if (always_checksum && am_sender && S_ISREG(st.st_mode)) {
		file_checksum(thisname, &st, tmp_sum);
		if (sender_keeps_checksum)
			extra_len += SUM_EXTRA_CNT * EXTRA_LEN;
	}

#if EXTRA_ROUNDING > 0
	if (extra_len & (EXTRA_ROUNDING * EXTRA_LEN))
		extra_len = (extra_len | (EXTRA_ROUNDING * EXTRA_LEN)) + EXTRA_LEN;
#endif

	alloc_len = FILE_STRUCT_LEN + extra_len + basename_len
		  + linkname_len;
	if (pool)
		bp = pool_alloc(pool, alloc_len, "make_file");
	else
		bp = new_array(char, alloc_len);

	memset(bp, 0, extra_len + FILE_STRUCT_LEN);
	bp += extra_len;
	file = (struct file_struct *)bp;
	bp += FILE_STRUCT_LEN;

	memcpy(bp, basename, basename_len);

#ifdef SUPPORT_HARD_LINKS
	if (preserve_hard_links && flist && flist->prev) {
		if (protocol_version >= 28
		 ? (!S_ISDIR(st.st_mode) && st.st_nlink > 1)
		 : S_ISREG(st.st_mode)) {
			tmp_dev = (int64)st.st_dev;
			tmp_ino = (int64)st.st_ino;
		} else
			tmp_dev = -1;
	}
#endif

#ifdef HAVE_STRUCT_STAT_ST_RDEV
	if (IS_DEVICE(st.st_mode)) {
		tmp_rdev = st.st_rdev;
		st.st_size = 0;
	} else if (IS_SPECIAL(st.st_mode))
		st.st_size = 0;
#endif

	file->flags = flags;
	file->modtime = st.st_mtime;
#ifdef ST_MTIME_NSEC
	if (st.ST_MTIME_NSEC && protocol_version >= 31) {
		file->flags |= FLAG_MOD_NSEC;
		F_MOD_NSEC(file) = st.ST_MTIME_NSEC;
	}
#endif
	file->len32 = (uint32)st.st_size;
#if SIZEOF_CAPITAL_OFF_T >= 8
	if (st.st_size > 0xFFFFFFFFu && S_ISREG(st.st_mode)) {
		file->flags |= FLAG_LENGTH64;
		F_HIGH_LEN(file) = (uint32)(st.st_size >> 32);
	}
#endif
	file->mode = st.st_mode;
	if (preserve_uid)
		F_OWNER(file) = st.st_uid;
	if (preserve_gid)
		F_GROUP(file) = st.st_gid;
	if (am_generator && st.st_uid == our_uid)
		file->flags |= FLAG_OWNED_BY_US;
	if (atimes_ndx && !S_ISDIR(file->mode))
		F_ATIME(file) = st.st_atime;
#ifdef SUPPORT_CRTIMES
	if (crtimes_ndx)
		F_CRTIME(file) = get_create_time(fname, &st);
#endif

	if (basename != thisname)
		file->dirname = lastdir;

#ifdef SUPPORT_LINKS
	if (linkname_len)
		memcpy(bp + basename_len, linkname, linkname_len);
#endif

	if (am_sender)
		F_PATHNAME(file) = pathname;
	else if (!pool)
		F_DEPTH(file) = extra_len / EXTRA_LEN;

	if (basename_len == 0+1) {
		if (!pool)
			unmake_file(file);
		return NULL;
	}

	if (sender_keeps_checksum && S_ISREG(st.st_mode))
		memcpy(F_SUM(file), tmp_sum, flist_csum_len);

	if (unsort_ndx)
		F_NDX(file) = stats.num_dirs;

	return file;
}

OFF_T get_device_size(int fd, const char *fname)
{
	OFF_T off = lseek(fd, 0, SEEK_END);

	if (off == (OFF_T) -1) {
		rsyserr(FERROR, errno, "failed to get device size via seek: %s", fname);
		return 0;
	}
	if (lseek(fd, 0, SEEK_SET) != 0)
		rsyserr(FERROR, errno, "failed to seek device back to start: %s", fname);

	return off;
}

/* Only called for temporary file_struct entries created by make_file(). */
void unmake_file(struct file_struct *file)
{
	free(REQ_EXTRA(file, F_DEPTH(file)));
}

static struct file_struct *send_file_name(int f, struct file_list *flist,
					  const char *fname, STRUCT_STAT *stp,
					  int flags, int filter_level)
{
	struct file_struct *file;

	file = make_file(fname, flist, stp, flags, filter_level);
	if (!file)
		return NULL;

	if (chmod_modes && !S_ISLNK(file->mode) && file->mode)
		file->mode = tweak_mode(file->mode, chmod_modes);

	if (f >= 0) {
		char fbuf[MAXPATHLEN];
#ifdef SUPPORT_LINKS
		const char *symlink_name;
		int symlink_len;
#ifdef ICONV_OPTION
		char symlink_buf[MAXPATHLEN];
#endif
#endif
#if defined SUPPORT_ACLS || defined SUPPORT_XATTRS
		stat_x sx;
		init_stat_x(&sx);
#endif

#ifdef SUPPORT_LINKS
		if (preserve_links && S_ISLNK(file->mode)) {
			symlink_name = F_SYMLINK(file);
			symlink_len = strlen(symlink_name);
			if (symlink_len == 0) {
				io_error |= IOERR_GENERAL;
				f_name(file, fbuf);
				rprintf(FERROR_XFER,
				    "skipping symlink with 0-length value: %s\n",
				    full_fname(fbuf));
				return NULL;
			}
		} else {
			symlink_name = NULL;
			symlink_len = 0;
		}
#endif

#ifdef ICONV_OPTION
		if (ic_send != (iconv_t)-1) {
			xbuf outbuf, inbuf;

			INIT_CONST_XBUF(outbuf, fbuf);

			if (file->dirname) {
				INIT_XBUF_STRLEN(inbuf, (char*)file->dirname);
				outbuf.size -= 2; /* Reserve room for '/' & 1 more char. */
				if (iconvbufs(ic_send, &inbuf, &outbuf, ICB_INIT) < 0)
					goto convert_error;
				outbuf.size += 2;
				fbuf[outbuf.len++] = '/';
			}

			INIT_XBUF_STRLEN(inbuf, (char*)file->basename);
			if (iconvbufs(ic_send, &inbuf, &outbuf, ICB_INIT) < 0) {
			  convert_error:
				io_error |= IOERR_GENERAL;
				rprintf(FERROR_XFER,
				    "[%s] cannot convert filename: %s (%s)\n",
				    who_am_i(), f_name(file, fbuf), strerror(errno));
				return NULL;
			}
			fbuf[outbuf.len] = '\0';

#ifdef SUPPORT_LINKS
			if (symlink_len && sender_symlink_iconv) {
				INIT_XBUF(inbuf, (char*)symlink_name, symlink_len, (size_t)-1);
				INIT_CONST_XBUF(outbuf, symlink_buf);
				if (iconvbufs(ic_send, &inbuf, &outbuf, ICB_INIT) < 0) {
					io_error |= IOERR_GENERAL;
					f_name(file, fbuf);
					rprintf(FERROR_XFER,
					    "[%s] cannot convert symlink data for: %s (%s)\n",
					    who_am_i(), full_fname(fbuf), strerror(errno));
					return NULL;
				}
				symlink_buf[outbuf.len] = '\0';

				symlink_name = symlink_buf;
				symlink_len = outbuf.len;
			}
#endif
		} else
#endif
			f_name(file, fbuf);

#ifdef SUPPORT_ACLS
		if (preserve_acls && !S_ISLNK(file->mode)) {
			sx.st.st_mode = file->mode;
			if (get_acl(fname, &sx) < 0) {
				io_error |= IOERR_GENERAL;
				return NULL;
			}
		}
#endif
#ifdef SUPPORT_XATTRS
		if (preserve_xattrs) {
			sx.st.st_mode = file->mode;
			if (get_xattr(fname, &sx) < 0) {
				io_error |= IOERR_GENERAL;
				return NULL;
			}
		}
#endif

		send_file_entry(f, fbuf, file,
#ifdef SUPPORT_LINKS
				symlink_name, symlink_len,
#endif
				flist->used, flist->ndx_start);

#ifdef SUPPORT_ACLS
		if (preserve_acls && !S_ISLNK(file->mode)) {
			send_acl(f, &sx);
			free_acl(&sx);
		}
#endif
#ifdef SUPPORT_XATTRS
		if (preserve_xattrs) {
			F_XATTR(file) = send_xattr(f, &sx);
			free_xattr(&sx);
		}
#endif
	}

	maybe_emit_filelist_progress(flist->used + flist_count_offset);

	flist_expand(flist, 1);
	flist->files[flist->used++] = file;

	return file;
}

static void send_if_directory(int f, struct file_list *flist,
			      struct file_struct *file,
			      char *fbuf, unsigned int ol,
			      int flags)
{
	char is_dot_dir = fbuf[ol-1] == '.' && (ol == 1 || fbuf[ol-2] == '/');

	if (S_ISDIR(file->mode)
	    && !(file->flags & FLAG_MOUNT_DIR) && f_name(file, fbuf)) {
		void *save_filters;
		unsigned int len = strlen(fbuf);
		if (len > 1 && fbuf[len-1] == '/')
			fbuf[--len] = '\0';
		save_filters = push_local_filters(fbuf, len);
		send_directory(f, flist, fbuf, len, flags);
		pop_local_filters(save_filters);
		fbuf[ol] = '\0';
		if (is_dot_dir)
			fbuf[ol-1] = '.';
	}
}

static int file_compare(const void *file1, const void *file2)
{
	return f_name_cmp(*(struct file_struct **)file1,
			  *(struct file_struct **)file2);
}

/* The guts of a merge-sort algorithm.  This was derived from the glibc
 * version, but I (Wayne) changed the merge code to do less copying and
 * to require only half the amount of temporary memory. */
static void fsort_tmp(struct file_struct **fp, size_t num,
		      struct file_struct **tmp)
{
	struct file_struct **f1, **f2, **t;
	size_t n1, n2;

	n1 = num / 2;
	n2 = num - n1;
	f1 = fp;
	f2 = fp + n1;

	if (n1 > 1)
		fsort_tmp(f1, n1, tmp);
	if (n2 > 1)
		fsort_tmp(f2, n2, tmp);

	while (f_name_cmp(*f1, *f2) <= 0) {
		if (!--n1)
			return;
		f1++;
	}

	t = tmp;
	memcpy(t, f1, n1 * PTR_SIZE);

	*f1++ = *f2++, n2--;

	while (n1 > 0 && n2 > 0) {
		if (f_name_cmp(*t, *f2) <= 0)
			*f1++ = *t++, n1--;
		else
			*f1++ = *f2++, n2--;
	}

	if (n1 > 0)
		memcpy(f1, t, n1 * PTR_SIZE);
}

/* This file-struct sorting routine makes sure that any identical names in
 * the file list stay in the same order as they were in the original list.
 * This is particularly vital in inc_recurse mode where we expect a sort
 * on the flist to match the exact order of a sort on the dir_flist. */
static void fsort(struct file_struct **fp, size_t num)
{
	if (num <= 1)
		return;

	if (use_qsort)
		qsort(fp, num, PTR_SIZE, file_compare);
	else {
		struct file_struct **tmp = new_array(struct file_struct *, (num+1) / 2);
		fsort_tmp(fp, num, tmp);
		free(tmp);
	}
}

/* We take an entire set of sibling dirs from the sorted flist and link them
 * into the tree, setting the appropriate parent/child/sibling pointers. */
static void add_dirs_to_tree(int parent_ndx, struct file_list *from_flist,
			     int dir_cnt)
{
	int i;
	int32 *dp = NULL;
	int32 *parent_dp = parent_ndx < 0 ? NULL
			 : F_DIR_NODE_P(dir_flist->sorted[parent_ndx]);

	/* The sending side is adding entries to dir_flist in sorted order, so sorted & files are the same. */
	flist_expand(dir_flist, dir_cnt);
	dir_flist->sorted = dir_flist->files;

	for (i = 0; dir_cnt; i++) {
		struct file_struct *file = from_flist->sorted[i];

		if (!S_ISDIR(file->mode))
			continue;

		dir_flist->files[dir_flist->used++] = file;
		dir_cnt--;

		if (file->basename[0] == '.' && file->basename[1] == '\0')
			continue;

		if (dp)
			DIR_NEXT_SIBLING(dp) = dir_flist->used - 1;
		else if (parent_dp)
			DIR_FIRST_CHILD(parent_dp) = dir_flist->used - 1;
		else
			send_dir_ndx = dir_flist->used - 1;

		dp = F_DIR_NODE_P(file);
		DIR_PARENT(dp) = parent_ndx;
		DIR_FIRST_CHILD(dp) = -1;
	}
	if (dp)
		DIR_NEXT_SIBLING(dp) = -1;
}

static void interpret_stat_error(const char *fname, int is_dir)
{
	if (errno == ENOENT) {
		io_error |= IOERR_VANISHED;
		rprintf(FWARNING, "%s has vanished: %s\n",
			is_dir ? "directory" : "file", full_fname(fname));
	} else {
		io_error |= IOERR_GENERAL;
		rsyserr(FERROR_XFER, errno, "link_stat %s failed",
			full_fname(fname));
	}
}

/* This function is normally called by the sender, but the receiving side also
 * calls it from get_dirlist() with f set to -1 so that we just construct the
 * file list in memory without sending it over the wire.  Also, get_dirlist()
 * might call this with f set to -2, which also indicates that local filter
 * rules should be ignored. */
static void send_directory(int f, struct file_list *flist, char *fbuf, int len,
			   int flags)
{
	struct dirent *di;
	unsigned remainder;
	char *p;
	DIR *d;
	int divert_dirs = (flags & FLAG_DIVERT_DIRS) != 0;
	int start = flist->used;
	int filter_level = f == -2 ? SERVER_FILTERS : ALL_FILTERS;

	assert(flist != NULL);

	if (!(d = opendir(fbuf))) {
		if (errno == ENOENT) {
			if (am_sender) /* Can abuse this for vanished error w/ENOENT: */
				interpret_stat_error(fbuf, True);
			return;
		}
		if (errno == ENOTDIR && (flags & FLAG_PERHAPS_DIR))
			return;
		io_error |= IOERR_GENERAL;
		rsyserr(FERROR_XFER, errno, "opendir %s failed", full_fname(fbuf));
		return;
	}

	p = fbuf + len;
	if (len == 1 && *fbuf == '/')
		remainder = MAXPATHLEN - 1;
	else if (len < MAXPATHLEN-1) {
		*p++ = '/';
		*p = '\0';
		remainder = MAXPATHLEN - (len + 1);
	} else
		remainder = 0;

	for (errno = 0, di = readdir(d); di; errno = 0, di = readdir(d)) {
		unsigned name_len;
		char *dname = d_name(di);
		if (dname[0] == '.' && (dname[1] == '\0'
		    || (dname[1] == '.' && dname[2] == '\0')))
			continue;
		name_len = strlcpy(p, dname, remainder);
		if (name_len >= remainder) {
			char save = fbuf[len];
			fbuf[len] = '\0';
			io_error |= IOERR_GENERAL;
			rprintf(FERROR_XFER,
				"filename overflows max-path len by %u: %s/%s\n",
				name_len - remainder + 1, fbuf, dname);
			fbuf[len] = save;
			continue;
		}
		if (dname[0] == '\0') {
			io_error |= IOERR_GENERAL;
			rprintf(FERROR_XFER,
				"cannot send file with empty name in %s\n",
				full_fname(fbuf));
			continue;
		}

		send_file_name(f, flist, fbuf, NULL, flags, filter_level);
	}

	fbuf[len] = '\0';

	if (errno) {
		io_error |= IOERR_GENERAL;
		rsyserr(FERROR_XFER, errno, "readdir(%s)", full_fname(fbuf));
	}

	closedir(d);

	if (f >= 0 && recurse && !divert_dirs) {
		int i, end = flist->used - 1;
		/* send_if_directory() bumps flist->used, so use "end". */
		for (i = start; i <= end; i++)
			send_if_directory(f, flist, flist->files[i], fbuf, len, flags);
	}
}

static void send_implied_dirs(int f, struct file_list *flist, char *fname,
			      char *start, char *limit, int flags, char name_type)
{
	static char lastpath[MAXPATHLEN] = "";
	static int lastpath_len = 0;
	static struct file_struct *lastpath_struct = NULL;
	struct file_struct *file;
	item_list *relname_list;
	relnamecache **rnpp;
	int len, need_new_dir, depth = 0;
	filter_rule_list save_filter_list = filter_list;

	flags = (flags | FLAG_IMPLIED_DIR) & ~(FLAG_TOP_DIR | FLAG_CONTENT_DIR);
	filter_list.head = filter_list.tail = NULL; /* Don't filter implied dirs. */

	if (inc_recurse) {
		if (lastpath_struct && F_PATHNAME(lastpath_struct) == pathname
		 && lastpath_len == limit - fname
		 && strncmp(lastpath, fname, lastpath_len) == 0)
			need_new_dir = 0;
		else
			need_new_dir = 1;
	} else {
		char *tp = fname, *lp = lastpath;
		/* Skip any initial directories in our path that we
		 * have in common with lastpath. */
		assert(start == fname);
		for ( ; ; tp++, lp++) {
			if (tp == limit) {
				if (*lp == '/' || *lp == '\0')
					goto done;
				break;
			}
			if (*lp != *tp)
				break;
			if (*tp == '/') {
				start = tp;
				depth++;
			}
		}
		need_new_dir = 1;
	}

	if (need_new_dir) {
		int save_copy_links = copy_links;
		int save_xfer_dirs = xfer_dirs;
		char *slash;

		copy_links = xfer_dirs = 1;

		*limit = '\0';

		for (slash = start; (slash = strchr(slash+1, '/')) != NULL; ) {
			*slash = '\0';
			file = send_file_name(f, flist, fname, NULL, flags, ALL_FILTERS);
			depth++;
			if (!inc_recurse && file && S_ISDIR(file->mode))
				change_local_filter_dir(fname, strlen(fname), depth);
			*slash = '/';
		}

		file = send_file_name(f, flist, fname, NULL, flags, ALL_FILTERS);
		if (inc_recurse) {
			if (file && !S_ISDIR(file->mode))
				file = NULL;
			lastpath_struct = file;
		} else if (file && S_ISDIR(file->mode))
			change_local_filter_dir(fname, strlen(fname), ++depth);

		strlcpy(lastpath, fname, sizeof lastpath);
		lastpath_len = limit - fname;

		*limit = '/';

		copy_links = save_copy_links;
		xfer_dirs = save_xfer_dirs;

		if (!inc_recurse)
			goto done;
	}

	if (!lastpath_struct)
		goto done; /* dir must have vanished */

	len = strlen(limit+1);
	memcpy(&relname_list, F_DIR_RELNAMES_P(lastpath_struct), sizeof relname_list);
	if (!relname_list) {
		relname_list = new0(item_list);
		memcpy(F_DIR_RELNAMES_P(lastpath_struct), &relname_list, sizeof relname_list);
	}
	rnpp = EXPAND_ITEM_LIST(relname_list, relnamecache *, 32);
	*rnpp = (relnamecache*)new_array(char, RELNAMECACHE_LEN + len + 1);
	(*rnpp)->name_type = name_type;
	strlcpy((*rnpp)->fname, limit+1, len + 1);

done:
	filter_list = save_filter_list;
}

static NORETURN void fatal_unsafe_io_error(void)
{
	/* This (sadly) can only happen when pushing data because
	 * the sender does not know about what kind of delete
	 * is in effect on the receiving side when pulling. */
	rprintf(FERROR_XFER, "FATAL I/O ERROR: dying to avoid a --delete-%s issue with a pre-3.0.7 receiver.\n",
		delete_during == 2 ? "delay" : "during");
	exit_cleanup(RERR_UNSUPPORTED);
}

static void send1extra(int f, struct file_struct *file, struct file_list *flist)
{
	char fbuf[MAXPATHLEN];
	item_list *relname_list;
	int len, dlen, flags = FLAG_DIVERT_DIRS | FLAG_CONTENT_DIR;
	size_t j;

	f_name(file, fbuf);
	dlen = strlen(fbuf);

	if (!change_pathname(file, NULL, 0))
		exit_cleanup(RERR_FILESELECT);

	change_local_filter_dir(fbuf, dlen, send_dir_depth);

	if (file->flags & FLAG_CONTENT_DIR) {
		if (one_file_system) {
			STRUCT_STAT st;
			if (link_stat(fbuf, &st, copy_dirlinks) != 0) {
				interpret_stat_error(fbuf, True);
				return;
			}
			filesystem_dev = st.st_dev;
		}
		send_directory(f, flist, fbuf, dlen, flags);
	}

	if (!relative_paths)
		return;

	memcpy(&relname_list, F_DIR_RELNAMES_P(file), sizeof relname_list);
	if (!relname_list)
		return;

	for (j = 0; j < relname_list->count; j++) {
		char *slash;
		relnamecache *rnp = ((relnamecache**)relname_list->items)[j];
		char name_type = rnp->name_type;

		fbuf[dlen] = '/';
		len = strlcpy(fbuf + dlen + 1, rnp->fname, sizeof fbuf - dlen - 1);
		free(rnp);
		if (len >= (int)sizeof fbuf)
			continue; /* Impossible... */

		slash = strchr(fbuf+dlen+1, '/');
		if (slash) {
			send_implied_dirs(f, flist, fbuf, fbuf+dlen+1, slash, flags, name_type);
			continue;
		}

		if (name_type != NORMAL_NAME) {
			STRUCT_STAT st;
			if (name_type == MISSING_NAME)
				memset(&st, 0, sizeof st);
			else if (link_stat(fbuf, &st, 1) != 0) {
				interpret_stat_error(fbuf, True);
				continue;
			}
			send_file_name(f, flist, fbuf, &st, FLAG_TOP_DIR | flags, ALL_FILTERS);
		} else
			send_file_name(f, flist, fbuf, NULL, FLAG_TOP_DIR | flags, ALL_FILTERS);
	}

	free(relname_list);
}

static void write_end_of_flist(int f, int send_io_error)
{
	if (xfer_flags_as_varint) {
		write_varint(f, 0);
		write_varint(f, send_io_error ? io_error : 0);
	} else if (send_io_error) {
		write_shortint(f, XMIT_EXTENDED_FLAGS|XMIT_IO_ERROR_ENDLIST);
		write_varint(f, io_error);
	} else
		write_byte(f, 0);
}

void send_extra_file_list(int f, int at_least)
{
	struct file_list *flist;
	int64 start_write;
	uint16 prev_flags;
	int save_io_error = io_error;

	if (flist_eof)
		return;

	if (at_least < 0)
		at_least = file_total - file_old_total + 1;

	/* Keep sending data until we have the requested number of
	 * files in the upcoming file-lists. */
	while (file_total - file_old_total < at_least) {
		struct file_struct *file = dir_flist->sorted[send_dir_ndx];
		int dir_ndx, dstart = stats.num_dirs;
		const char *pathname = F_PATHNAME(file);
		int32 *dp;

		flist = flist_new(0, "send_extra_file_list");
		start_write = stats.total_written;

		if (unsort_ndx)
			dir_ndx = F_NDX(file);
		else
			dir_ndx = send_dir_ndx;
		write_ndx(f, NDX_FLIST_OFFSET - dir_ndx);
		flist->parent_ndx = send_dir_ndx; /* the sending side must remember the sorted ndx value */

		send1extra(f, file, flist);
		prev_flags = file->flags;
		dp = F_DIR_NODE_P(file);

		/* If there are any duplicate directory names that follow, we
		 * send all the dirs together in one file-list.  The dir_flist
		 * tree links all the child subdirs onto the last dup dir. */
		while ((dir_ndx = DIR_NEXT_SIBLING(dp)) >= 0
		    && dir_flist->sorted[dir_ndx]->flags & FLAG_DUPLICATE) {
			send_dir_ndx = dir_ndx;
			file = dir_flist->sorted[dir_ndx];
			/* Try to avoid some duplicate scanning of identical dirs. */
			if (F_PATHNAME(file) == pathname && prev_flags & FLAG_CONTENT_DIR)
				file->flags &= ~FLAG_CONTENT_DIR;
			send1extra(f, file, flist);
			prev_flags = file->flags;
			dp = F_DIR_NODE_P(file);
		}

		if (io_error == save_io_error || ignore_errors)
			write_end_of_flist(f, 0);
		else if (use_safe_inc_flist)
			write_end_of_flist(f, 1);
		else {
			if (delete_during)
				fatal_unsafe_io_error();
			write_end_of_flist(f, 0);
		}

		if (need_unsorted_flist) {
			flist->sorted = new_array(struct file_struct *, flist->used);
			memcpy(flist->sorted, flist->files, flist->used * PTR_SIZE);
		} else
			flist->sorted = flist->files;

		flist_sort_and_clean(flist, 0);

		add_dirs_to_tree(send_dir_ndx, flist, stats.num_dirs - dstart);
		flist_done_allocating(flist);

		file_total += flist->used;
		stats.flist_size += stats.total_written - start_write;
		stats.num_files += flist->used;
		if (DEBUG_GTE(FLIST, 3))
			output_flist(flist);

		if (DIR_FIRST_CHILD(dp) >= 0) {
			send_dir_ndx = DIR_FIRST_CHILD(dp);
			send_dir_depth++;
		} else {
			while (DIR_NEXT_SIBLING(dp) < 0) {
				if ((send_dir_ndx = DIR_PARENT(dp)) < 0) {
					write_ndx(f, NDX_FLIST_EOF);
					flist_eof = 1;
					if (DEBUG_GTE(FLIST, 3))
						rprintf(FINFO, "[%s] flist_eof=1\n", who_am_i());
					change_local_filter_dir(NULL, 0, 0);
					goto finish;
				}
				send_dir_depth--;
				file = dir_flist->sorted[send_dir_ndx];
				dp = F_DIR_NODE_P(file);
			}
			send_dir_ndx = DIR_NEXT_SIBLING(dp);
		}
	}

  finish:
	if (io_error != save_io_error && protocol_version == 30 && !ignore_errors)
		send_msg_int(MSG_IO_ERROR, io_error);
}

struct file_list *send_file_list(int f, int argc, char *argv[])
{
	static const char *lastdir;
	static int lastdir_len = -1;
	int len, dirlen;
	STRUCT_STAT st;
	char *p, *dir;
	struct file_list *flist;
	struct timeval start_tv, end_tv;
	int64 start_write;
	int use_ff_fd = 0;
	int disable_buffering, reenable_multiplex = -1;
	int flags = recurse ? FLAG_CONTENT_DIR : 0;
	int reading_remotely = filesfrom_host != NULL;
	int rl_flags = (reading_remotely ? 0 : RL_DUMP_COMMENTS)
#ifdef ICONV_OPTION
		     | (filesfrom_convert ? RL_CONVERT : 0)
#endif
		     | (eol_nulls || reading_remotely ? RL_EOL_NULLS : 0);
	int implied_dot_dir = 0;

	rprintf(FLOG, "building file list\n");
	if (show_filelist_progress)
		start_filelist_progress("building file list");
	else if (inc_recurse && INFO_GTE(FLIST, 1) && !am_server)
		rprintf(FCLIENT, "sending incremental file list\n");

	start_write = stats.total_written;
	gettimeofday(&start_tv, NULL);

	if (relative_paths && protocol_version >= 30)
		implied_dirs = 1; /* We send flagged implied dirs */

#ifdef SUPPORT_HARD_LINKS
	if (preserve_hard_links && protocol_version >= 30 && !cur_flist)
		init_hard_links();
#endif

	flist = cur_flist = flist_new(0, "send_file_list");
	flist_expand(flist, FLIST_START_LARGE);
	if (inc_recurse) {
		dir_flist = flist_new(FLIST_TEMP, "send_file_list");
		flist_expand(dir_flist, FLIST_START_LARGE);
		flags |= FLAG_DIVERT_DIRS;
	} else
		dir_flist = cur_flist;

	disable_buffering = io_start_buffering_out(f);
	if (filesfrom_fd >= 0) {
		if (argv[0] && !change_dir(argv[0], CD_NORMAL)) {
			rsyserr(FERROR_XFER, errno, "change_dir %s failed",
				full_fname(argv[0]));
			exit_cleanup(RERR_FILESELECT);
		}
		if (protocol_version < 31) {
			/* Older protocols send the files-from data w/o packaging
			 * it in multiplexed I/O packets, so temporarily switch
			 * to buffered I/O to match this behavior. */
			reenable_multiplex = io_end_multiplex_in(MPLX_TO_BUFFERED);
		}
		use_ff_fd = 1;
	}

	if (!orig_dir)
		orig_dir = strdup(curr_dir);

	while (1) {
		char fbuf[MAXPATHLEN], *fn, name_type;

		if (use_ff_fd) {
			if (read_line(filesfrom_fd, fbuf, sizeof fbuf, rl_flags) == 0)
				break;
			sanitize_path(fbuf, fbuf, "", 0, SP_KEEP_DOT_DIRS);
		} else {
			if (argc-- == 0)
				break;
			strlcpy(fbuf, *argv++, MAXPATHLEN);
			if (sanitize_paths)
				sanitize_path(fbuf, fbuf, "", 0, SP_KEEP_DOT_DIRS);
		}

		len = strlen(fbuf);
		if (relative_paths) {
			/* We clean up fbuf below. */
			name_type = NORMAL_NAME;
		} else if (!len || fbuf[len - 1] == '/') {
			if (len == 2 && fbuf[0] == '.') {
				/* Turn "./" into just "." rather than "./." */
				fbuf[--len] = '\0';
			} else {
				if (len + 1 >= MAXPATHLEN)
					overflow_exit("send_file_list");
				fbuf[len++] = '.';
				fbuf[len] = '\0';
			}
			name_type = DOTDIR_NAME;
		} else if (len > 1 && fbuf[len-1] == '.' && fbuf[len-2] == '.'
		    && (len == 2 || fbuf[len-3] == '/')) {
			if (len + 2 >= MAXPATHLEN)
				overflow_exit("send_file_list");
			fbuf[len++] = '/';
			fbuf[len++] = '.';
			fbuf[len] = '\0';
			name_type = DOTDIR_NAME;
		} else if (fbuf[len-1] == '.' && (len == 1 || fbuf[len-2] == '/'))
			name_type = DOTDIR_NAME;
		else
			name_type = NORMAL_NAME;

		dir = NULL;

		if (!relative_paths) {
			p = strrchr(fbuf, '/');
			if (p) {
				*p = '\0';
				if (p == fbuf)
					dir = "/";
				else
					dir = fbuf;
				len -= p - fbuf + 1;
				fn = p + 1;
			} else
				fn = fbuf;
		} else {
			if ((p = strstr(fbuf, "/./")) != NULL) {
				*p = '\0';
				if (p == fbuf)
					dir = "/";
				else {
					dir = fbuf;
					clean_fname(dir, 0);
				}
				fn = p + 3;
				while (*fn == '/')
					fn++;
				if (!*fn)
					*--fn = '\0'; /* ensure room for '.' */
			} else
				fn = fbuf;
			/* A leading ./ can be used in relative mode to affect
			 * the dest dir without its name being in the path. */
			if (*fn == '.' && fn[1] == '/' && fn[2] && !implied_dot_dir)
				implied_dot_dir = -1;
			len = clean_fname(fn, CFN_KEEP_TRAILING_SLASH
					    | CFN_DROP_TRAILING_DOT_DIR);
			if (len == 1) {
				if (fn[0] == '/') {
					fn = "/.";
					len = 2;
					name_type = DOTDIR_NAME;
				} else if (fn[0] == '.')
					name_type = DOTDIR_NAME;
			} else if (fn[len-1] == '/') {
				fn[--len] = '\0';
				if (len == 1 && *fn == '.')
					name_type = DOTDIR_NAME;
				else
					name_type = SLASH_ENDING_NAME;
			}
			/* Reject a ".." dir in the active part of the path. */
			for (p = fn; (p = strstr(p, "..")) != NULL; p += 2) {
				if ((p[2] == '/' || p[2] == '\0')
				 && (p == fn || p[-1] == '/')) {
					rprintf(FERROR,
					    "found \"..\" dir in relative path: %s\n",
					    fn);
					exit_cleanup(RERR_SYNTAX);
				}
			}
		}

		if (!*fn) {
			len = 1;
			fn = ".";
			name_type = DOTDIR_NAME;
		}

		dirlen = dir ? strlen(dir) : 0;
		if (dirlen != lastdir_len || (dirlen && memcmp(lastdir, dir, dirlen) != 0)) {
			if (!change_pathname(NULL, dir, -dirlen))
				goto bad_path;
			lastdir = pathname;
			lastdir_len = pathname_len;
		} else if (!change_pathname(NULL, lastdir, lastdir_len)) {
		    bad_path:
			if (implied_dot_dir < 0)
				implied_dot_dir = 0;
			continue;
		}

		if (implied_dot_dir < 0) {
			implied_dot_dir = 1;
			send_file_name(f, flist, ".", NULL, (flags | FLAG_IMPLIED_DIR) & ~FLAG_CONTENT_DIR, ALL_FILTERS);
		}

		if (fn != fbuf)
			memmove(fbuf, fn, len + 1);

		if (link_stat(fbuf, &st, copy_dirlinks || name_type != NORMAL_NAME) != 0
		 || (name_type != DOTDIR_NAME && is_excluded(fbuf, S_ISDIR(st.st_mode) != 0, SERVER_FILTERS))
		 || (relative_paths && path_is_daemon_excluded(fbuf, 1))) {
			if (errno != ENOENT || missing_args == 0) {
				/* This is a transfer error, but inhibit deletion
				 * only if we might be omitting an existing file. */
				if (errno != ENOENT)
					io_error |= IOERR_GENERAL;
				rsyserr(FERROR_XFER, errno, "link_stat %s failed",
					full_fname(fbuf));
				continue;
			} else if (missing_args == 1) {
				/* Just ignore the arg. */
				continue;
			} else /* (missing_args == 2) */ {
				/* Send the arg as a "missing" entry with
				 * mode 0, which tells the generator to delete it. */
				memset(&st, 0, sizeof st);
			}
		}

		/* A dot-dir should not be excluded! */
		if (name_type != DOTDIR_NAME && st.st_mode != 0
		 && is_excluded(fbuf, S_ISDIR(st.st_mode) != 0, ALL_FILTERS))
			continue;

		if (S_ISDIR(st.st_mode) && !xfer_dirs) {
			rprintf(FINFO, "skipping directory %s\n", fbuf);
			continue;
		}

		if (inc_recurse && relative_paths && *fbuf) {
			if ((p = strchr(fbuf+1, '/')) != NULL) {
				if (p - fbuf == 1 && *fbuf == '.') {
					if ((fn = strchr(p+1, '/')) != NULL)
						p = fn;
				} else
					fn = p;
				send_implied_dirs(f, flist, fbuf, fbuf, p, flags,
						  IS_MISSING_FILE(st) ? MISSING_NAME : name_type);
				if (fn == p)
					continue;
			}
		} else if (implied_dirs && (p=strrchr(fbuf,'/')) && p != fbuf) {
			/* Send the implied directories at the start of the
			 * source spec, so we get their permissions right. */
			send_implied_dirs(f, flist, fbuf, fbuf, p, flags, 0);
		}

		if (one_file_system)
			filesystem_dev = st.st_dev;

		if (recurse || (xfer_dirs && name_type != NORMAL_NAME)) {
			struct file_struct *file;
			file = send_file_name(f, flist, fbuf, &st,
					      FLAG_TOP_DIR | FLAG_CONTENT_DIR | flags,
					      NO_FILTERS);
			if (!file)
				continue;
			if (inc_recurse) {
				if (name_type == DOTDIR_NAME) {
					if (send_dir_depth < 0) {
						send_dir_depth = 0;
						change_local_filter_dir(fbuf, len, send_dir_depth);
					}
					send_directory(f, flist, fbuf, len, flags);
				}
			} else
				send_if_directory(f, flist, file, fbuf, len, flags);
		} else
			send_file_name(f, flist, fbuf, &st, flags, NO_FILTERS);
	}

	if (reenable_multiplex >= 0)
		io_start_multiplex_in(reenable_multiplex);

	gettimeofday(&end_tv, NULL);
	stats.flist_buildtime = (int64)(end_tv.tv_sec - start_tv.tv_sec) * 1000
			      + (end_tv.tv_usec - start_tv.tv_usec) / 1000;
	if (stats.flist_buildtime == 0)
		stats.flist_buildtime = 1;
	start_tv = end_tv;

	/* Indicate end of file list */
	if (io_error == 0 || ignore_errors)
		write_end_of_flist(f, 0);
	else if (use_safe_inc_flist)
		write_end_of_flist(f, 1);
	else {
		if (delete_during && inc_recurse)
			fatal_unsafe_io_error();
		write_end_of_flist(f, 0);
	}

#ifdef SUPPORT_HARD_LINKS
	if (preserve_hard_links && protocol_version >= 30 && !inc_recurse)
		idev_destroy();
#endif

	if (show_filelist_progress)
		finish_filelist_progress(flist);

	gettimeofday(&end_tv, NULL);
	stats.flist_xfertime = (int64)(end_tv.tv_sec - start_tv.tv_sec) * 1000
			     + (end_tv.tv_usec - start_tv.tv_usec) / 1000;

	/* When converting names, both sides keep an unsorted file-list array
	 * because the names will differ on the sending and receiving sides
	 * (both sides will use the unsorted index number for each item). */

	/* Sort the list without removing any duplicates.  This allows the
	 * receiving side to ask for whatever name it kept.  For incremental
	 * recursion mode, the sender marks duplicate dirs so that it can
	 * send them together in a single file-list. */
	if (need_unsorted_flist) {
		flist->sorted = new_array(struct file_struct *, flist->used);
		memcpy(flist->sorted, flist->files, flist->used * PTR_SIZE);
	} else
		flist->sorted = flist->files;
	flist_sort_and_clean(flist, 0);
	file_total += flist->used;
	file_old_total += flist->used;

	if (numeric_ids <= 0 && !inc_recurse)
		send_id_lists(f);

	/* send the io_error flag */
	if (protocol_version < 30)
		write_int(f, ignore_errors ? 0 : io_error);
	else if (!use_safe_inc_flist && io_error && !ignore_errors)
		send_msg_int(MSG_IO_ERROR, io_error);

	if (disable_buffering)
		io_end_buffering_out(IOBUF_FREE_BUFS);

	stats.flist_size = stats.total_written - start_write;
	stats.num_files = flist->used;

	if (DEBUG_GTE(FLIST, 3))
		output_flist(flist);

	if (DEBUG_GTE(FLIST, 2))
		rprintf(FINFO, "send_file_list done\n");

	if (inc_recurse) {
		send_dir_depth = 1;
		add_dirs_to_tree(-1, flist, stats.num_dirs);
		if (!file_total || strcmp(flist->sorted[flist->low]->basename, ".") != 0)
			flist->parent_ndx = -1;
		flist_done_allocating(flist);
		if (send_dir_ndx < 0) {
			write_ndx(f, NDX_FLIST_EOF);
			flist_eof = 1;
			if (DEBUG_GTE(FLIST, 3))
				rprintf(FINFO, "[%s] flist_eof=1\n", who_am_i());
		}
		else if (file_total == 1) {
			/* If we're creating incremental file-lists and there
			 * was just 1 item in the first file-list, send 1 more
			 * file-list to check if this is a 1-file xfer. */
			send_extra_file_list(f, 1);
		}
	} else {
		flist_eof = 1;
		if (DEBUG_GTE(FLIST, 3))
			rprintf(FINFO, "[%s] flist_eof=1\n", who_am_i());
	}

	return flist;
}

struct file_list *recv_file_list(int f, int dir_ndx)
{
	const char *good_dirname = NULL;
	struct file_list *flist;
	int dstart, flags;
	int64 start_read;

	if (!first_flist) {
		if (show_filelist_progress)
			start_filelist_progress("receiving file list");
		else if (inc_recurse && INFO_GTE(FLIST, 1) && !am_server)
			rprintf(FCLIENT, "receiving incremental file list\n");
		rprintf(FLOG, "receiving file list\n");
		if (usermap)
			parse_name_map(usermap, True);
		if (groupmap)
			parse_name_map(groupmap, False);
	}

	start_read = stats.total_read;

#ifdef SUPPORT_HARD_LINKS
	if (preserve_hard_links && !first_flist)
		init_hard_links();
#endif

	if (inc_recurse && dir_ndx >= 0) {
		if (dir_ndx >= dir_flist->used) {
			rprintf(FERROR_XFER, "rsync: refusing invalid dir_ndx %u >= %u\n", dir_ndx, dir_flist->used);
			exit_cleanup(RERR_PROTOCOL);
		}
		struct file_struct *file = dir_flist->files[dir_ndx];
		if (file->flags & FLAG_GOT_DIR_FLIST) {
			rprintf(FERROR_XFER, "rsync: refusing malicious duplicate flist for dir %d\n", dir_ndx);
			exit_cleanup(RERR_PROTOCOL);
		}
		file->flags |= FLAG_GOT_DIR_FLIST;
	}

	flist = flist_new(0, "recv_file_list");
	flist_expand(flist, FLIST_START_LARGE);

	if (inc_recurse) {
		if (flist->ndx_start == 1) {
			dir_flist = flist_new(FLIST_TEMP, "recv_file_list");
			flist_expand(dir_flist, FLIST_START_LARGE);
		}
		dstart = dir_flist->used;
	} else {
		dir_flist = flist;
		dstart = 0;
	}

	while (1) {
		struct file_struct *file;

		if (xfer_flags_as_varint) {
			if ((flags = read_varint(f)) == 0) {
				int err = read_varint(f);
				if (!ignore_errors)
					io_error |= err;
				break;
			}
		} else {
			if ((flags = read_byte(f)) == 0)
				break;

			if (protocol_version >= 28 && (flags & XMIT_EXTENDED_FLAGS))
				flags |= read_byte(f) << 8;

			if (flags == (XMIT_EXTENDED_FLAGS|XMIT_IO_ERROR_ENDLIST)) {
				int err;
				if (!use_safe_inc_flist) {
					rprintf(FERROR, "Invalid flist flag: %x\n", flags);
					exit_cleanup(RERR_PROTOCOL);
				}
				err = read_varint(f);
				if (!ignore_errors)
					io_error |= err;
				break;
			}
		}

		flist_expand(flist, 1);
		file = recv_file_entry(f, flist, flags);

		if (inc_recurse) {
			static const char empty_dir[] = "\0";
			const char *cur_dir = file->dirname ? file->dirname : empty_dir;
			if (relative_paths && *cur_dir == '/')
				cur_dir++;
			if (cur_dir != good_dirname) {
				const char *d = dir_ndx >= 0 ? f_name(dir_flist->files[dir_ndx], NULL) : empty_dir;
				if (strcmp(cur_dir, d) != 0) {
					rprintf(FERROR,
						"ABORTING due to invalid path from sender: %s/%s\n",
						cur_dir, file->basename);
					exit_cleanup(RERR_UNSUPPORTED);
				}
				good_dirname = cur_dir;
			}
		}

		if (S_ISREG(file->mode)) {
			/* Already counted */
		} else if (S_ISDIR(file->mode)) {
			if (inc_recurse) {
				flist_expand(dir_flist, 1);
				dir_flist->files[dir_flist->used++] = file;
			}
			stats.num_dirs++;
		} else if (S_ISLNK(file->mode))
			stats.num_symlinks++;
		else if (IS_DEVICE(file->mode))
			stats.num_devices++;
		else
			stats.num_specials++;

		flist->files[flist->used++] = file;

		maybe_emit_filelist_progress(flist->used);

		if (DEBUG_GTE(FLIST, 2)) {
			char *name = f_name(file, NULL);
			rprintf(FINFO, "recv_file_name(%s)\n", NS(name));
		}
	}
	file_total += flist->used;

	if (DEBUG_GTE(FLIST, 2))
		rprintf(FINFO, "received %d names\n", flist->used);

	if (show_filelist_progress)
		finish_filelist_progress(flist);

	if (need_unsorted_flist) {
		/* Create an extra array of index pointers that we can sort for
		 * the generator's use (for wading through the files in sorted
		 * order and for calling flist_find()).  We keep the "files"
		 * list unsorted for our exchange of index numbers with the
		 * other side (since their names may not sort the same). */
		flist->sorted = new_array(struct file_struct *, flist->used);
		memcpy(flist->sorted, flist->files, flist->used * PTR_SIZE);
		if (inc_recurse && dir_flist->used > dstart) {
			static int dir_flist_malloced = 0;
			if (dir_flist_malloced < dir_flist->malloced) {
				dir_flist->sorted = realloc_array(dir_flist->sorted,
							struct file_struct *,
							dir_flist->malloced);
				dir_flist_malloced = dir_flist->malloced;
			}
			memcpy(dir_flist->sorted + dstart, dir_flist->files + dstart,
			       (dir_flist->used - dstart) * PTR_SIZE);
			fsort(dir_flist->sorted + dstart, dir_flist->used - dstart);
		}
	} else {
		flist->sorted = flist->files;
		if (inc_recurse && dir_flist->used > dstart) {
			dir_flist->sorted = dir_flist->files;
			fsort(dir_flist->sorted + dstart, dir_flist->used - dstart);
		}
	}

	if (inc_recurse)
		flist_done_allocating(flist);
	else if (f >= 0) {
		recv_id_list(f, flist);
		flist_eof = 1;
		if (DEBUG_GTE(FLIST, 3))
			rprintf(FINFO, "[%s] flist_eof=1\n", who_am_i());
	}

	/* The --relative option sends paths with a leading slash, so we need
	 * to specify the strip_root option here.  We rejected leading slashes
	 * for a non-relative transfer in recv_file_entry(). */
	flist_sort_and_clean(flist, relative_paths);

	if (protocol_version < 30) {
		/* Recv the io_error flag */
		int err = read_int(f);
		if (!ignore_errors)
			io_error |= err;
	} else if (inc_recurse && flist->ndx_start == 1) {
		if (!file_total || strcmp(flist->sorted[flist->low]->basename, ".") != 0)
			flist->parent_ndx = -1;
	}

	if (DEBUG_GTE(FLIST, 3))
		output_flist(flist);

	if (DEBUG_GTE(FLIST, 2))
		rprintf(FINFO, "recv_file_list done\n");

	stats.flist_size += stats.total_read - start_read;
	stats.num_files += flist->used;

	return flist;
}

/* This is only used once by the receiver if the very first file-list
 * has exactly one item in it. */
void recv_additional_file_list(int f)
{
	struct file_list *flist;
	int ndx = read_ndx(f);
	if (ndx == NDX_FLIST_EOF) {
		flist_eof = 1;
		if (DEBUG_GTE(FLIST, 3))
			rprintf(FINFO, "[%s] flist_eof=1\n", who_am_i());
		change_local_filter_dir(NULL, 0, 0);
	} else {
		ndx = NDX_FLIST_OFFSET - ndx;
		if (ndx < 0 || ndx >= dir_flist->used) {
			ndx = NDX_FLIST_OFFSET - ndx;
			rprintf(FERROR,
				"[%s] Invalid dir index: %d (%d - %d)\n",
				who_am_i(), ndx, NDX_FLIST_OFFSET,
				NDX_FLIST_OFFSET - dir_flist->used + 1);
			exit_cleanup(RERR_PROTOCOL);
		}
		if (DEBUG_GTE(FLIST, 3)) {
			rprintf(FINFO, "[%s] receiving flist for dir %d\n",
				who_am_i(), ndx);
		}
		flist = recv_file_list(f, ndx);
		flist->parent_ndx = ndx;
	}
}

/* Search for an identically-named item in the file list.  Note that the
 * items must agree in their directory-ness, or no match is returned. */
int flist_find(struct file_list *flist, struct file_struct *f)
{
	int low = flist->low, high = flist->high;
	int diff, mid, mid_up;

	while (low <= high) {
		mid = (low + high) / 2;
		if (F_IS_ACTIVE(flist->sorted[mid]))
			mid_up = mid;
		else {
			/* Scan for the next non-empty entry using the cached
			 * distance values.  If the value isn't fully up-to-
			 * date, update it. */
			mid_up = mid + F_DEPTH(flist->sorted[mid]);
			if (!F_IS_ACTIVE(flist->sorted[mid_up])) {
				do {
				    mid_up += F_DEPTH(flist->sorted[mid_up]);
				} while (!F_IS_ACTIVE(flist->sorted[mid_up]));
				F_DEPTH(flist->sorted[mid]) = mid_up - mid;
			}
			if (mid_up > high) {
				/* If there's nothing left above us, set high to
				 * a non-empty entry below us and continue. */
				high = mid - (int)flist->sorted[mid]->len32;
				if (!F_IS_ACTIVE(flist->sorted[high])) {
					do {
					    high -= (int)flist->sorted[high]->len32;
					} while (!F_IS_ACTIVE(flist->sorted[high]));
					flist->sorted[mid]->len32 = mid - high;
				}
				continue;
			}
		}
		diff = f_name_cmp(flist->sorted[mid_up], f);
		if (diff == 0) {
			if (protocol_version < 29
			    && S_ISDIR(flist->sorted[mid_up]->mode)
			    != S_ISDIR(f->mode))
				return -1;
			return mid_up;
		}
		if (diff < 0)
			low = mid_up + 1;
		else
			high = mid - 1;
	}
	return -1;
}

/* Search for a name in the file list.  You must specify want_dir_match as:
 * 1=match directories, 0=match non-directories, or -1=match either. */
int flist_find_name(struct file_list *flist, const char *fname, int want_dir_match)
{
	static struct file_struct *f;
	char fbuf[MAXPATHLEN];
	const char *slash = strrchr(fname, '/');
	const char *basename = slash ? slash+1 : fname;

	if (!f)
		f = (struct file_struct*)new_array(char, FILE_STRUCT_LEN + MAXPATHLEN + 1);

	memset(f, 0, FILE_STRUCT_LEN);
	memcpy((void*)f->basename, basename, strlen(basename)+1);

	if (slash) {
		strlcpy(fbuf, fname, slash - fname + 1);
		f->dirname = fbuf;
	} else
		f->dirname = NULL;

	f->mode = want_dir_match > 0 ? S_IFDIR : S_IFREG;

	if (want_dir_match < 0)
		return flist_find_ignore_dirness(flist, f);
	return flist_find(flist, f);
}

/* Search for an identically-named item in the file list.  Differs from
 * flist_find in that an item that agrees with "f" in directory-ness is
 * preferred but one that does not is still found. */
int flist_find_ignore_dirness(struct file_list *flist, struct file_struct *f)
{
	mode_t save_mode;
	int ndx;

	/* First look for an item that agrees in directory-ness. */
	ndx = flist_find(flist, f);
	if (ndx >= 0)
		return ndx;

	/* Temporarily flip f->mode to look for an item of opposite
	 * directory-ness. */
	save_mode = f->mode;
	f->mode = S_ISDIR(f->mode) ? S_IFREG : S_IFDIR;
	ndx = flist_find(flist, f);
	f->mode = save_mode;
	return ndx;
}

/*
 * Free up any resources a file_struct has allocated
 * and clear the file.
 */
void clear_file(struct file_struct *file)
{
	/* The +1 zeros out the first char of the basename. */
	memset(file, 0, FILE_STRUCT_LEN + 1);
	/* In an empty entry, F_DEPTH() is an offset to the next non-empty
	 * entry.  Likewise for len32 in the opposite direction.  We assume
	 * that we're alone for now since flist_find() will adjust the counts
	 * it runs into that aren't up-to-date. */
	file->len32 = F_DEPTH(file) = 1;
}

/* Allocate a new file list. */
static struct file_list *flist_new(int flags, const char *msg)
{
	struct file_list *flist;

	flist = new0(struct file_list);

	if (flags & FLIST_TEMP) {
		if (!(flist->file_pool = pool_create(SMALL_EXTENT, 0, _out_of_memory, POOL_INTERN)))
			out_of_memory(msg);
	} else {
		/* This is a doubly linked list with prev looping back to
		 * the end of the list, but the last next pointer is NULL. */
		if (!first_flist) {
			if (!(flist->file_pool = pool_create(NORMAL_EXTENT, 0, _out_of_memory, POOL_INTERN)))
				out_of_memory(msg);

			flist->ndx_start = flist->flist_num = inc_recurse ? 1 : 0;

			first_flist = cur_flist = flist->prev = flist;
		} else {
			struct file_list *prev = first_flist->prev;

			flist->file_pool = first_flist->file_pool;

			flist->ndx_start = prev->ndx_start + prev->used + 1;
			flist->flist_num = prev->flist_num + 1;

			flist->prev = prev;
			prev->next = first_flist->prev = flist;
		}
		flist->pool_boundary = pool_boundary(flist->file_pool, 0);
		flist_cnt++;
	}

	return flist;
}

/* Free up all elements in a flist. */
void flist_free(struct file_list *flist)
{
	if (!flist->prev) {
		/* Was FLIST_TEMP dir-list. */
	} else if (flist == flist->prev) {
		first_flist = cur_flist = NULL;
		file_total = 0;
		flist_cnt = 0;
	} else {
		if (flist == cur_flist)
			cur_flist = flist->next;
		if (flist == first_flist)
			first_flist = first_flist->next;
		else {
			flist->prev->next = flist->next;
			if (!flist->next)
				flist->next = first_flist;
		}
		flist->next->prev = flist->prev;
		file_total -= flist->used;
		flist_cnt--;
	}

	if (!flist->prev || !flist_cnt)
		pool_destroy(flist->file_pool);
	else
		pool_free_old(flist->file_pool, flist->pool_boundary);

	if (flist->sorted && flist->sorted != flist->files)
		free(flist->sorted);
	free(flist->files);
	free(flist);
}

/* This routine ensures we don't have any duplicate names in our file list.
 * duplicate names can cause corruption because of the pipelining. */
static void flist_sort_and_clean(struct file_list *flist, int strip_root)
{
	char fbuf[MAXPATHLEN];
	int i, prev_i;

	if (!flist)
		return;
	if (flist->used == 0) {
		flist->high = -1;
		flist->low = 0;
		return;
	}

	fsort(flist->sorted, flist->used);

	if (!am_sender || inc_recurse) {
		for (i = prev_i = 0; i < flist->used; i++) {
			if (F_IS_ACTIVE(flist->sorted[i])) {
				prev_i = i;
				break;
			}
		}
		flist->low = prev_i;
	} else {
		i = prev_i = flist->used - 1;
		flist->low = 0;
	}

	while (++i < flist->used) {
		int j;
		struct file_struct *file = flist->sorted[i];

		if (!F_IS_ACTIVE(file))
			continue;
		if (f_name_cmp(file, flist->sorted[prev_i]) == 0)
			j = prev_i;
		else if (protocol_version >= 29 && S_ISDIR(file->mode)) {
			int save_mode = file->mode;
			/* Make sure that this directory doesn't duplicate a
			 * non-directory earlier in the list. */
			flist->high = prev_i;
			file->mode = S_IFREG;
			j = flist_find(flist, file);
			file->mode = save_mode;
		} else
			j = -1;
		if (j >= 0) {
			int keep, drop;
			/* If one is a dir and the other is not, we want to
			 * keep the dir because it might have contents in the
			 * list.  Otherwise keep the first one. */
			if (S_ISDIR(file->mode)) {
				struct file_struct *fp = flist->sorted[j];
				if (!S_ISDIR(fp->mode))
					keep = i, drop = j;
				else {
					if (am_sender)
						file->flags |= FLAG_DUPLICATE;
					else { /* Make sure we merge our vital flags. */
						fp->flags |= file->flags & (FLAG_TOP_DIR|FLAG_CONTENT_DIR);
						fp->flags &= file->flags | ~FLAG_IMPLIED_DIR;
					}
					keep = j, drop = i;
				}
			} else
				keep = j, drop = i;

			if (!am_sender) {
				if (DEBUG_GTE(DUP, 1)) {
					rprintf(FINFO,
					    "removing duplicate name %s from file list (%d)\n",
					    f_name(file, fbuf), drop + flist->ndx_start);
				}
				clear_file(flist->sorted[drop]);
			}

			if (keep == i) {
				if (flist->low == drop) {
					for (j = drop + 1;
					     j < i && !F_IS_ACTIVE(flist->sorted[j]);
					     j++) {}
					flist->low = j;
				}
				prev_i = i;
			}
		} else
			prev_i = i;
	}
	flist->high = prev_i;

	if (strip_root) {
		/* We need to strip off the leading slashes for relative
		 * paths, but this must be done _after_ the sorting phase. */
		for (i = flist->low; i <= flist->high; i++) {
			struct file_struct *file = flist->sorted[i];

			if (!file->dirname)
				continue;
			while (*file->dirname == '/')
				file->dirname++;
			if (!*file->dirname)
				file->dirname = NULL;
		}
	}

	if (prune_empty_dirs && !am_sender) {
		int j, prev_depth = 0;

		prev_i = 0; /* It's OK that this isn't really true. */

		for (i = flist->low; i <= flist->high; i++) {
			struct file_struct *fp, *file = flist->sorted[i];

			/* This temporarily abuses the F_DEPTH() value for a
			 * directory that is in a chain that might get pruned.
			 * We restore the old value if it gets a reprieve. */
			if (S_ISDIR(file->mode) && F_DEPTH(file)) {
				/* Dump empty dirs when coming back down. */
				for (j = prev_depth; j >= F_DEPTH(file); j--) {
					fp = flist->sorted[prev_i];
					if (F_DEPTH(fp) >= 0)
						break;
					prev_i = -F_DEPTH(fp)-1;
					clear_file(fp);
				}
				prev_depth = F_DEPTH(file);
				if (is_excluded(f_name(file, fbuf), 1, ALL_FILTERS)) {
					/* Keep dirs through this dir. */
					for (j = prev_depth-1; ; j--) {
						fp = flist->sorted[prev_i];
						if (F_DEPTH(fp) >= 0)
							break;
						prev_i = -F_DEPTH(fp)-1;
						F_DEPTH(fp) = j;
					}
				} else
					F_DEPTH(file) = -prev_i-1;
				prev_i = i;
			} else {
				/* Keep dirs through this non-dir. */
				for (j = prev_depth; ; j--) {
					fp = flist->sorted[prev_i];
					if (F_DEPTH(fp) >= 0)
						break;
					prev_i = -F_DEPTH(fp)-1;
					F_DEPTH(fp) = j;
				}
			}
		}
		/* Dump all remaining empty dirs. */
		while (1) {
			struct file_struct *fp = flist->sorted[prev_i];
			if (F_DEPTH(fp) >= 0)
				break;
			prev_i = -F_DEPTH(fp)-1;
			clear_file(fp);
		}

		for (i = flist->low; i <= flist->high; i++) {
			if (F_IS_ACTIVE(flist->sorted[i]))
				break;
		}
		flist->low = i;
		for (i = flist->high; i >= flist->low; i--) {
			if (F_IS_ACTIVE(flist->sorted[i]))
				break;
		}
		flist->high = i;
	}
}

static void output_flist(struct file_list *flist)
{
	char uidbuf[16], gidbuf[16], depthbuf[16];
	struct file_struct *file;
	const char *root, *dir, *slash, *name, *trail;
	const char *who = who_am_i();
	int i;

	rprintf(FINFO, "[%s] flist start=%d, used=%d, low=%d, high=%d\n",
		who, flist->ndx_start, flist->used, flist->low, flist->high);
	for (i = 0; i < flist->used; i++) {
		file = flist->files[i];
		if ((am_root || am_sender) && uid_ndx) {
			snprintf(uidbuf, sizeof uidbuf, " uid=%u",
				 F_OWNER(file));
		} else
			*uidbuf = '\0';
		if (gid_ndx) {
			static char parens[] = "(\0)\0\0\0";
			char *pp = parens + (file->flags & FLAG_SKIP_GROUP ? 0 : 3);
			snprintf(gidbuf, sizeof gidbuf, " gid=%s%u%s",
				 pp, F_GROUP(file), pp + 2);
		} else
			*gidbuf = '\0';
		if (!am_sender)
			snprintf(depthbuf, sizeof depthbuf, "%d", F_DEPTH(file));
		if (F_IS_ACTIVE(file)) {
			root = am_sender ? NS(F_PATHNAME(file)) : depthbuf;
			if ((dir = file->dirname) == NULL)
				dir = slash = "";
			else
				slash = "/";
			name = file->basename;
			trail = S_ISDIR(file->mode) ? "/" : "";
		} else
			root = dir = slash = name = trail = "";
		rprintf(FINFO,
			"[%s] i=%d %s %s%s%s%s mode=0%o len=%s%s%s flags=%x\n",
			who, i + flist->ndx_start,
			root, dir, slash, name, trail,
			(int)file->mode, comma_num(F_LENGTH(file)),
			uidbuf, gidbuf, file->flags);
	}
}

enum fnc_state { s_DIR, s_SLASH, s_BASE, s_TRAILING };
enum fnc_type { t_PATH, t_ITEM };

static int found_prefix;

/* Compare the names of two file_struct entities, similar to how strcmp()
 * would do if it were operating on the joined strings.
 *
 * Some differences beginning with protocol_version 29: (1) directory names
 * are compared with an assumed trailing slash so that they compare in a
 * way that would cause them to sort immediately prior to any content they
 * may have; (2) a directory of any name compares after a non-directory of
 * any name at the same depth; (3) a directory with name "." compares prior
 * to anything else.  These changes mean that a directory and a non-dir
 * with the same name will not compare as equal (protocol_version >= 29).
 *
 * The dirname component can be an empty string, but the basename component
 * cannot (and never is in the current codebase).  The basename component
 * may be NULL (for a removed item), in which case it is considered to be
 * after any existing item. */
int f_name_cmp(const struct file_struct *f1, const struct file_struct *f2)
{
	int dif;
	const uchar *c1, *c2;
	enum fnc_state state1, state2;
	enum fnc_type type1, type2;
	enum fnc_type t_path = protocol_version >= 29 ? t_PATH : t_ITEM;

	if (!f1 || !F_IS_ACTIVE(f1)) {
		if (!f2 || !F_IS_ACTIVE(f2))
			return 0;
		return -1;
	}
	if (!f2 || !F_IS_ACTIVE(f2))
		return 1;

	c1 = (uchar*)f1->dirname;
	c2 = (uchar*)f2->dirname;
	if (c1 == c2)
		c1 = c2 = NULL;
	if (!c1) {
		type1 = S_ISDIR(f1->mode) ? t_path : t_ITEM;
		c1 = (const uchar*)f1->basename;
		if (type1 == t_PATH && *c1 == '.' && !c1[1]) {
			type1 = t_ITEM;
			state1 = s_TRAILING;
			c1 = (uchar*)"";
		} else
			state1 = s_BASE;
	} else {
		type1 = t_path;
		state1 = s_DIR;
	}
	if (!c2) {
		type2 = S_ISDIR(f2->mode) ? t_path : t_ITEM;
		c2 = (const uchar*)f2->basename;
		if (type2 == t_PATH && *c2 == '.' && !c2[1]) {
			type2 = t_ITEM;
			state2 = s_TRAILING;
			c2 = (uchar*)"";
		} else
			state2 = s_BASE;
	} else {
		type2 = t_path;
		state2 = s_DIR;
	}

	if (type1 != type2)
		return type1 == t_PATH ? 1 : -1;

	do {
		if (!*c1) {
			switch (state1) {
			case s_DIR:
				state1 = s_SLASH;
				c1 = (uchar*)"/";
				break;
			case s_SLASH:
				type1 = S_ISDIR(f1->mode) ? t_path : t_ITEM;
				c1 = (const uchar*)f1->basename;
				if (type1 == t_PATH && *c1 == '.' && !c1[1]) {
					type1 = t_ITEM;
					state1 = s_TRAILING;
					c1 = (uchar*)"";
				} else
					state1 = s_BASE;
				break;
			case s_BASE:
				state1 = s_TRAILING;
				if (type1 == t_PATH) {
					c1 = (uchar*)"/";
					break;
				}
				/* FALL THROUGH */
			case s_TRAILING:
				type1 = t_ITEM;
				break;
			}
			if (*c2 && type1 != type2)
				return type1 == t_PATH ? 1 : -1;
		}
		if (!*c2) {
			switch (state2) {
			case s_DIR:
				state2 = s_SLASH;
				c2 = (uchar*)"/";
				break;
			case s_SLASH:
				type2 = S_ISDIR(f2->mode) ? t_path : t_ITEM;
				c2 = (const uchar*)f2->basename;
				if (type2 == t_PATH && *c2 == '.' && !c2[1]) {
					type2 = t_ITEM;
					state2 = s_TRAILING;
					c2 = (uchar*)"";
				} else
					state2 = s_BASE;
				break;
			case s_BASE:
				state2 = s_TRAILING;
				if (type2 == t_PATH) {
					c2 = (uchar*)"/";
					break;
				}
				/* FALL THROUGH */
			case s_TRAILING:
				found_prefix = 1;
				if (!*c1)
					return 0;
				type2 = t_ITEM;
				break;
			}
			if (type1 != type2)
				return type1 == t_PATH ? 1 : -1;
		}
	} while ((dif = (int)*c1++ - (int)*c2++) == 0);

	return dif;
}

/* Returns 1 if f1's filename has all of f2's filename as a prefix.  This does
 * not match if f2's basename is not an exact match of a path element in f1.
 * E.g. /path/foo is not a prefix of /path/foobar/baz, but /path/foobar is. */
int f_name_has_prefix(const struct file_struct *f1, const struct file_struct *f2)
{
	found_prefix = 0;
	f_name_cmp(f1, f2);
	return found_prefix;
}

char *f_name_buf(void)
{
	static char names[5][MAXPATHLEN];
	static unsigned int n;

	n = (n + 1) % (sizeof names / sizeof names[0]);

	return names[n];
}

/* Return a copy of the full filename of a flist entry, using the indicated
 * buffer or one of 5 static buffers if fbuf is NULL.  No size-checking is
 * done because we checked the size when creating the file_struct entry.
 */
char *f_name(const struct file_struct *f, char *fbuf)
{
	if (!f || !F_IS_ACTIVE(f))
		return NULL;

	if (!fbuf)
		fbuf = f_name_buf();

	if (f->dirname) {
		int len = strlen(f->dirname);
		memcpy(fbuf, f->dirname, len);
		fbuf[len] = '/';
		strlcpy(fbuf + len + 1, f->basename, MAXPATHLEN - (len + 1));
	} else
		strlcpy(fbuf, f->basename, MAXPATHLEN);

	return fbuf;
}

/* Do a non-recursive scan of the named directory, possibly ignoring all
 * exclude rules except for the daemon's.  If "dlen" is >=0, it is the length
 * of the dirname string, and also indicates that "dirname" is a MAXPATHLEN
 * buffer (the functions we call will append names onto the end, but the old
 * dir value will be restored on exit). */
struct file_list *get_dirlist(char *dirname, int dlen, int flags)
{
	struct file_list *dirlist;
	char dirbuf[MAXPATHLEN];
	int save_recurse = recurse;
	int save_xfer_dirs = xfer_dirs;
	int save_prune_empty_dirs = prune_empty_dirs;
	int senddir_fd = flags & GDL_IGNORE_FILTER_RULES ? -2 : -1;
	int senddir_flags = FLAG_CONTENT_DIR;

	if (dlen < 0) {
		dlen = strlcpy(dirbuf, dirname, MAXPATHLEN);
		if (dlen >= MAXPATHLEN)
			return NULL;
		dirname = dirbuf;
	}

	dirlist = flist_new(FLIST_TEMP, "get_dirlist");

	if (flags & GDL_PERHAPS_DIR)
		senddir_flags |= FLAG_PERHAPS_DIR;

	recurse = 0;
	xfer_dirs = 1;
	send_directory(senddir_fd, dirlist, dirname, dlen, senddir_flags);
	xfer_dirs = save_xfer_dirs;
	recurse = save_recurse;
	if (INFO_GTE(PROGRESS, 1))
		flist_count_offset += dirlist->used;

	prune_empty_dirs = 0;
	dirlist->sorted = dirlist->files;
	flist_sort_and_clean(dirlist, 0);
	prune_empty_dirs = save_prune_empty_dirs;

	if (DEBUG_GTE(FLIST, 3))
		output_flist(dirlist);

	return dirlist;
}
/*
 * Routines that are exclusive to the generator process.
 *
 * Copyright (C) 1996-2000 Andrew Tridgell
 * Copyright (C) 1996 Paul Mackerras
 * Copyright (C) 2002 Martin Pool <mbp@samba.org>
 * Copyright (C) 2003-2023 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"
#include "inums.h"
#include "ifuncs.h"

extern int dry_run;
extern int do_xfers;
extern int stdout_format_has_i;
extern int logfile_format_has_i;
extern int am_root;
extern int am_server;
extern int am_daemon;
extern int inc_recurse;
extern int relative_paths;
extern int implied_dirs;
extern int keep_dirlinks;
extern int write_devices;
extern int preserve_acls;
extern int preserve_xattrs;
extern int preserve_links;
extern int preserve_devices;
extern int preserve_specials;
extern int preserve_hard_links;
extern int preserve_executability;
extern int preserve_perms;
extern int preserve_mtimes;
extern int omit_dir_times;
extern int omit_link_times;
extern int delete_mode;
extern int delete_before;
extern int delete_during;
extern int delete_after;
extern int missing_args;
extern int msgdone_cnt;
extern int ignore_errors;
extern int remove_source_files;
extern int delay_updates;
extern int update_only;
extern int human_readable;
extern int ignore_existing;
extern int ignore_non_existing;
extern int want_xattr_optim;
extern int modify_window;
extern int inplace;
extern int append_mode;
extern int make_backups;
extern int csum_length;
extern int ignore_times;
extern int size_only;
extern OFF_T max_size;
extern OFF_T min_size;
extern int io_error;
extern int flist_eof;
extern int allowed_lull;
extern int sock_f_out;
extern int protocol_version;
extern int file_total;
extern int fuzzy_basis;
extern int always_checksum;
extern int flist_csum_len;
extern char *partial_dir;
extern int alt_dest_type;
extern int whole_file;
extern int list_only;
extern int read_batch;
extern int write_batch;
extern int safe_symlinks;
extern int32 block_size;
extern int unsort_ndx;
extern int max_delete;
extern int force_delete;
extern int one_file_system;
extern int skipped_deletes;
extern dev_t filesystem_dev;
extern mode_t orig_umask;
extern uid_t our_uid;
extern char *tmpdir;
extern char *basis_dir[MAX_BASIS_DIRS+1];
extern struct file_list *cur_flist, *first_flist, *dir_flist;
extern filter_rule_list filter_list, daemon_filter_list;

int maybe_ATTRS_REPORT = 0;
int maybe_ATTRS_ACCURATE_TIME = 0;

static dev_t dev_zero;
static int deldelay_size = 0, deldelay_cnt = 0;
static char *deldelay_buf = NULL;
static int deldelay_fd = -1;
static int loopchk_limit;
static int dir_tweaking;
static int symlink_timeset_failed_flags;
static int need_retouch_dir_times;
static int need_retouch_dir_perms;
static const char *solo_file = NULL;

/* Forward declarations. */
#ifdef SUPPORT_HARD_LINKS
static void handle_skipped_hlink(struct file_struct *file, int itemizing,
				 enum logcode code, int f_out);
#endif

#define EARLY_DELAY_DONE_MSG() (!delay_updates)
#define EARLY_DELETE_DONE_MSG() (!(delete_during == 2 || delete_after))

static int start_delete_delay_temp(void)
{
	char fnametmp[MAXPATHLEN];
	int save_dry_run = dry_run;

	dry_run = 0;
	if (!get_tmpname(fnametmp, "deldelay", False)
	 || (deldelay_fd = do_mkstemp(fnametmp, 0600)) < 0) {
		rprintf(FINFO, "NOTE: Unable to create delete-delay temp file%s.\n",
			inc_recurse ? "" : " -- switching to --delete-after");
		delete_during = 0;
		delete_after = !inc_recurse;
		dry_run = save_dry_run;
		return 0;
	}
	unlink(fnametmp);
	dry_run = save_dry_run;
	return 1;
}

static int flush_delete_delay(void)
{
	if (deldelay_fd < 0 && !start_delete_delay_temp())
		return 0;
	if (write(deldelay_fd, deldelay_buf, deldelay_cnt) != deldelay_cnt) {
		rsyserr(FERROR, errno, "flush of delete-delay buffer");
		delete_during = 0;
		delete_after = !inc_recurse;
		close(deldelay_fd);
		return 0;
	}
	deldelay_cnt = 0;
	return 1;
}

static int remember_delete(struct file_struct *file, const char *fname, int flags)
{
	int len;

	if (deldelay_cnt == deldelay_size && !flush_delete_delay())
		return 0;

	if (flags & DEL_NO_UID_WRITE)
		deldelay_buf[deldelay_cnt++] = '!';

	while (1) {
		len = snprintf(deldelay_buf + deldelay_cnt, deldelay_size - deldelay_cnt,
			       "%x %s%c", (int)file->mode, fname, '\0');
		if ((deldelay_cnt += len) <= deldelay_size)
			break;
		deldelay_cnt -= len;
		if (!flush_delete_delay())
			return 0;
	}

	return 1;
}

static int read_delay_line(char *buf, int *flags_p)
{
	static int read_pos = 0;
	unsigned int mode;
	int j, len;
	char *bp, *past_space;

	while (1) {
		for (j = read_pos; j < deldelay_cnt && deldelay_buf[j]; j++) {}
		if (j < deldelay_cnt)
			break;
		if (deldelay_fd < 0) {
			if (j > read_pos)
				goto invalid_data;
			return -1;
		}
		deldelay_cnt -= read_pos;
		if (deldelay_cnt == deldelay_size)
			goto invalid_data;
		if (deldelay_cnt && read_pos) {
			memmove(deldelay_buf, deldelay_buf + read_pos,
				deldelay_cnt);
		}
		len = read(deldelay_fd, deldelay_buf + deldelay_cnt,
			   deldelay_size - deldelay_cnt);
		if (len == 0) {
			if (deldelay_cnt) {
				rprintf(FERROR, "ERROR: unexpected EOF in delete-delay file.\n");
			}
			return -1;
		}
		if (len < 0) {
			rsyserr(FERROR, errno,
				"reading delete-delay file");
			return -1;
		}
		deldelay_cnt += len;
		read_pos = 0;
	}

	bp = deldelay_buf + read_pos;
	if (*bp == '!') {
		bp++;
		*flags_p = DEL_NO_UID_WRITE;
	} else
		*flags_p = 0;

	if (sscanf(bp, "%x ", &mode) != 1) {
	  invalid_data:
		rprintf(FERROR, "ERROR: invalid data in delete-delay file.\n");
		return -1;
	}
	past_space = strchr(bp, ' ') + 1;
	len = j - read_pos - (past_space - bp) + 1; /* count the '\0' */
	read_pos = j + 1;

	if (len > MAXPATHLEN) {
		rprintf(FERROR, "ERROR: filename too long in delete-delay file.\n");
		return -1;
	}

	/* The caller needs the name in a MAXPATHLEN buffer, so we copy it
	 * instead of returning a pointer to our buffer. */
	memcpy(buf, past_space, len);

	return mode;
}

static void do_delayed_deletions(char *delbuf)
{
	int mode, flags;

	if (deldelay_fd >= 0) {
		if (deldelay_cnt && !flush_delete_delay())
			return;
		lseek(deldelay_fd, 0, 0);
	}
	while ((mode = read_delay_line(delbuf, &flags)) >= 0)
		delete_item(delbuf, mode, flags | DEL_RECURSE);
	if (deldelay_fd >= 0)
		close(deldelay_fd);
}

/* This function is used to implement per-directory deletion, and is used by
 * all the --delete-WHEN options.  Note that the fbuf pointer must point to a
 * MAXPATHLEN buffer with the name of the directory in it (the functions we
 * call will append names onto the end, but the old dir value will be restored
 * on exit). */
static void delete_in_dir(char *fbuf, struct file_struct *file, dev_t fs_dev)
{
	static int already_warned = 0;
	static struct hashtable *dev_tbl;
	struct file_list *dirlist;
	char delbuf[MAXPATHLEN];
	int dlen, i;

	if (!fbuf) {
		change_local_filter_dir(NULL, 0, 0);
		return;
	}

	if (DEBUG_GTE(DEL, 2))
		rprintf(FINFO, "delete_in_dir(%s)\n", fbuf);

	if (allowed_lull)
		maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH);

	if (io_error & IOERR_GENERAL && !ignore_errors) {
		if (already_warned)
			return;
		rprintf(FINFO,
			"IO error encountered -- skipping file deletion\n");
		already_warned = 1;
		return;
	}

	dlen = strlen(fbuf);
	change_local_filter_dir(fbuf, dlen, F_DEPTH(file));

	if (one_file_system) {
		if (!dev_tbl)
			dev_tbl = hashtable_create(16, HT_KEY64);
		if (file->flags & FLAG_TOP_DIR) {
			hashtable_find(dev_tbl, fs_dev+1, "");
			filesystem_dev = fs_dev;
		} else if (filesystem_dev != fs_dev) {
			if (!hashtable_find(dev_tbl, fs_dev+1, NULL))
				return;
			filesystem_dev = fs_dev; /* it's a prior top-dir dev */
		}
	}

	dirlist = get_dirlist(fbuf, dlen, 0);

	/* If an item in dirlist is not found in flist, delete it
	 * from the filesystem. */
	for (i = dirlist->used; i--; ) {
		struct file_struct *fp = dirlist->files[i];
		if (!F_IS_ACTIVE(fp))
			continue;
		if (fp->flags & FLAG_MOUNT_DIR && S_ISDIR(fp->mode)) {
			if (INFO_GTE(MOUNT, 1))
				rprintf(FINFO, "cannot delete mount point: %s\n",
					f_name(fp, NULL));
			continue;
		}
		/* Here we want to match regardless of file type.  Replacement
		 * of a file with one of another type is handled separately by
		 * a delete_item call with a DEL_MAKE_ROOM flag. */
		if (flist_find_ignore_dirness(cur_flist, fp) < 0) {
			int flags = DEL_RECURSE;
			if (!(fp->mode & S_IWUSR) && !am_root && fp->flags & FLAG_OWNED_BY_US)
				flags |= DEL_NO_UID_WRITE;
			f_name(fp, delbuf);
			if (delete_during == 2) {
				if (!remember_delete(fp, delbuf, flags))
					break;
			} else
				delete_item(delbuf, fp->mode, flags);
		}
	}

	flist_free(dirlist);
}

/* This deletes any files on the receiving side that are not present on the
 * sending side.  This is used by --delete-before and --delete-after. */
static void do_delete_pass(void)
{
	char fbuf[MAXPATHLEN];
	STRUCT_STAT st;
	int j;

	/* dry_run is incremented when the destination doesn't exist yet. */
	if (dry_run > 1 || list_only)
		return;

	for (j = 0; j < cur_flist->used; j++) {
		struct file_struct *file = cur_flist->sorted[j];

		if (!F_IS_ACTIVE(file))
			continue;

		f_name(file, fbuf);

		if (!(file->flags & FLAG_CONTENT_DIR)) {
			change_local_filter_dir(fbuf, strlen(fbuf), F_DEPTH(file));
			continue;
		}

		if (DEBUG_GTE(DEL, 1) && file->flags & FLAG_TOP_DIR)
			rprintf(FINFO, "deleting in %s\n", fbuf);

		if (link_stat(fbuf, &st, keep_dirlinks) < 0
		 || !S_ISDIR(st.st_mode))
			continue;

		delete_in_dir(fbuf, file, st.st_dev);
	}
	delete_in_dir(NULL, NULL, dev_zero);

	if (INFO_GTE(FLIST, 2) && !am_server)
		rprintf(FINFO, "                    \r");
}

static inline int mtime_differs(STRUCT_STAT *stp, struct file_struct *file)
{
#ifdef ST_MTIME_NSEC
	return !same_time(stp->st_mtime, stp->ST_MTIME_NSEC, file->modtime, F_MOD_NSEC_or_0(file));
#else
	return !same_time(stp->st_mtime, 0, file->modtime, 0);
#endif
}

static inline int any_time_differs(stat_x *sxp, struct file_struct *file, UNUSED(const char *fname))
{
	int differs = mtime_differs(&sxp->st, file);
#ifdef SUPPORT_CRTIMES
	if (!differs && crtimes_ndx) {
		if (sxp->crtime == 0)
			sxp->crtime = get_create_time(fname, &sxp->st);
		differs = !same_time(sxp->crtime, 0, F_CRTIME(file), 0);
	}
#endif
	return differs;
}

static inline int perms_differ(struct file_struct *file, stat_x *sxp)
{
	if (preserve_perms)
		return !BITS_EQUAL(sxp->st.st_mode, file->mode, CHMOD_BITS);

	if (preserve_executability)
		return (sxp->st.st_mode & 0111 ? 1 : 0) ^ (file->mode & 0111 ? 1 : 0);

	return 0;
}

static inline int ownership_differs(struct file_struct *file, stat_x *sxp)
{
	if (am_root && uid_ndx && sxp->st.st_uid != (uid_t)F_OWNER(file))
		return 1;

	if (gid_ndx && !(file->flags & FLAG_SKIP_GROUP) && sxp->st.st_gid != (gid_t)F_GROUP(file))
		return 1;

	return 0;
}

#ifdef SUPPORT_ACLS
static inline int acls_differ(const char *fname, struct file_struct *file, stat_x *sxp)
{
	if (preserve_acls) {
		if (!ACL_READY(*sxp))
			get_acl(fname, sxp);
		if (set_acl(NULL, file, sxp, file->mode))
			return 1;
	}

	return 0;
}
#endif

#ifdef SUPPORT_XATTRS
static inline int xattrs_differ(const char *fname, struct file_struct *file, stat_x *sxp)
{
	if (preserve_xattrs) {
		if (!XATTR_READY(*sxp))
			get_xattr(fname, sxp);
		if (xattr_diff(file, sxp, 0))
			return 1;
	}

	return 0;
}
#endif

int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
{
	if (S_ISLNK(file->mode)) {
#ifdef CAN_SET_SYMLINK_TIMES
		if (preserve_mtimes && !omit_link_times && any_time_differs(sxp, file, fname))
			return 0;
#endif
#ifdef CAN_CHMOD_SYMLINK
		if (perms_differ(file, sxp))
			return 0;
#endif
#ifdef CAN_CHOWN_SYMLINK
		if (ownership_differs(file, sxp))
			return 0;
#endif
#if defined SUPPORT_ACLS && 0 /* no current symlink-ACL support */
		if (acls_differ(fname, file, sxp))
			return 0;
#endif
#if defined SUPPORT_XATTRS && !defined NO_SYMLINK_XATTRS
		if (xattrs_differ(fname, file, sxp))
			return 0;
#endif
	} else {
		if (preserve_mtimes && any_time_differs(sxp, file, fname))
			return 0;
		if (perms_differ(file, sxp))
			return 0;
		if (ownership_differs(file, sxp))
			return 0;
#ifdef SUPPORT_ACLS
		if (acls_differ(fname, file, sxp))
			return 0;
#endif
#ifdef SUPPORT_XATTRS
		if (xattrs_differ(fname, file, sxp))
			return 0;
#endif
	}

	return 1;
}

void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statret,
	     stat_x *sxp, int32 iflags, uchar fnamecmp_type,
	     const char *xname)
{
	if (statret >= 0) { /* A from-dest-dir statret can == 1! */
		int keep_time = !preserve_mtimes ? 0
		    : S_ISDIR(file->mode) ? !omit_dir_times
		    : S_ISLNK(file->mode) ? !omit_link_times
		    : 1;

		if (S_ISREG(file->mode) && F_LENGTH(file) != sxp->st.st_size)
			iflags |= ITEM_REPORT_SIZE;
		if (file->flags & FLAG_TIME_FAILED) { /* symlinks only */
			if (iflags & ITEM_LOCAL_CHANGE)
				iflags |= symlink_timeset_failed_flags;
		} else if (keep_time
		 ? mtime_differs(&sxp->st, file)
		 : iflags & (ITEM_TRANSFER|ITEM_LOCAL_CHANGE) && !(iflags & ITEM_MATCHED)
		  && (!(iflags & ITEM_XNAME_FOLLOWS) || *xname))
			iflags |= ITEM_REPORT_TIME;
		if (atimes_ndx && !S_ISDIR(file->mode) && !S_ISLNK(file->mode)
		 && !same_time(F_ATIME(file), 0, sxp->st.st_atime, 0))
			iflags |= ITEM_REPORT_ATIME;
#ifdef SUPPORT_CRTIMES
		if (crtimes_ndx) {
			if (sxp->crtime == 0)
				sxp->crtime = get_create_time(fnamecmp, &sxp->st);
			if (!same_time(sxp->crtime, 0, F_CRTIME(file), 0))
				iflags |= ITEM_REPORT_CRTIME;
		}
#endif
#ifndef CAN_CHMOD_SYMLINK
		if (S_ISLNK(file->mode)) {
			;
		} else
#endif
		if (preserve_perms) {
			if (!BITS_EQUAL(sxp->st.st_mode, file->mode, CHMOD_BITS))
				iflags |= ITEM_REPORT_PERMS;
		} else if (preserve_executability
		 && ((sxp->st.st_mode & 0111 ? 1 : 0) ^ (file->mode & 0111 ? 1 : 0)))
			iflags |= ITEM_REPORT_PERMS;
		if (uid_ndx && am_root && (uid_t)F_OWNER(file) != sxp->st.st_uid)
			iflags |= ITEM_REPORT_OWNER;
		if (gid_ndx && !(file->flags & FLAG_SKIP_GROUP) && sxp->st.st_gid != (gid_t)F_GROUP(file))
			iflags |= ITEM_REPORT_GROUP;
#ifdef SUPPORT_ACLS
		if (preserve_acls && !S_ISLNK(file->mode)) {
			if (!ACL_READY(*sxp))
				get_acl(fnamecmp, sxp);
			if (set_acl(NULL, file, sxp, file->mode))
				iflags |= ITEM_REPORT_ACL;
		}
#endif
#ifdef SUPPORT_XATTRS
		if (preserve_xattrs) {
			if (!XATTR_READY(*sxp))
				get_xattr(fnamecmp, sxp);
			if (xattr_diff(file, sxp, 1))
				iflags |= ITEM_REPORT_XATTR;
		}
#endif
	} else {
#ifdef SUPPORT_XATTRS
		if (preserve_xattrs && xattr_diff(file, NULL, 1))
			iflags |= ITEM_REPORT_XATTR;
#endif
		iflags |= ITEM_IS_NEW;
	}

	iflags &= 0xffff;
	if ((iflags & (SIGNIFICANT_ITEM_FLAGS|ITEM_REPORT_XATTR) || INFO_GTE(NAME, 2)
	  || stdout_format_has_i > 1 || (xname && *xname)) && !read_batch) {
		if (protocol_version >= 29) {
			if (ndx >= 0)
				write_ndx(sock_f_out, ndx);
			write_shortint(sock_f_out, iflags);
			if (iflags & ITEM_BASIS_TYPE_FOLLOWS)
				write_byte(sock_f_out, fnamecmp_type);
			if (iflags & ITEM_XNAME_FOLLOWS)
				write_vstring(sock_f_out, xname, strlen(xname));
#ifdef SUPPORT_XATTRS
			if (preserve_xattrs && do_xfers
			 && iflags & (ITEM_REPORT_XATTR|ITEM_TRANSFER)) {
				int fd = iflags & ITEM_REPORT_XATTR
				      && !(want_xattr_optim && BITS_SET(iflags, ITEM_XNAME_FOLLOWS|ITEM_LOCAL_CHANGE))
				       ? sock_f_out : -1;
				send_xattr_request(NULL, file, fd);
			}
#endif
		} else if (ndx >= 0) {
			enum logcode code = logfile_format_has_i ? FINFO : FCLIENT;
			log_item(code, file, iflags, xname);
		}
	}
}

static enum filetype get_file_type(mode_t mode)
{
	if (S_ISREG(mode))
		return FT_REG;
	if (S_ISLNK(mode))
		return FT_SYMLINK;
	if (S_ISDIR(mode))
		return FT_DIR;
	if (IS_SPECIAL(mode))
		return FT_SPECIAL;
	if (IS_DEVICE(mode))
		return FT_DEVICE;
	return FT_UNSUPPORTED;
}

/* Perform our quick-check heuristic for determining if a file is unchanged. */
int quick_check_ok(enum filetype ftype, const char *fn, struct file_struct *file, STRUCT_STAT *st)
{
	switch (ftype) {
	  case FT_REG:
		if (st->st_size != F_LENGTH(file))
			return 0;

		/* If always_checksum is set then we use the checksum instead
		 * of the file mtime to determine whether to sync. */
		if (always_checksum > 0) {
			char sum[MAX_DIGEST_LEN];
			file_checksum(fn, st, sum);
			return memcmp(sum, F_SUM(file), flist_csum_len) == 0;
		}

		if (size_only > 0)
			return 1;

		if (ignore_times)
			return 0;

		if (mtime_differs(st, file))
			return 0;
		break;
	  case FT_DIR:
		break;
	  case FT_SYMLINK: {
#ifdef SUPPORT_LINKS
		char lnk[MAXPATHLEN];
		int len = do_readlink(fn, lnk, MAXPATHLEN-1);
		if (len <= 0)
			return 0;
		lnk[len] = '\0';
		if (strcmp(lnk, F_SYMLINK(file)) != 0)
			return 0;
		break;
#else
		return -1;
#endif
	  }
	  case FT_SPECIAL:
		if (!BITS_EQUAL(file->mode, st->st_mode, _S_IFMT))
			return 0;
		break;
	  case FT_DEVICE: {
		uint32 *devp = F_RDEV_P(file);
		if (st->st_rdev != MAKEDEV(DEV_MAJOR(devp), DEV_MINOR(devp)))
			return 0;
		break;
	  }
	  case FT_UNSUPPORTED:
		return -1;
	}
	return 1;
}

/*
 * set (initialize) the size entries in the per-file sum_struct
 * calculating dynamic block and checksum sizes.
 *
 * This is only called from generate_and_send_sums() but is a separate
 * function to encapsulate the logic.
 *
 * The block size is a rounded square root of file length.
 *
 * The checksum size is determined according to:
 *     blocksum_bits = BLOCKSUM_BIAS + 2*log2(file_len) - log2(block_len)
 * provided by Donovan Baarda which gives a probability of rsync
 * algorithm corrupting data and falling back using the whole md4
 * checksums.
 *
 * This might be made one of several selectable heuristics.
 */
static void sum_sizes_sqroot(struct sum_struct *sum, int64 len)
{
	int32 blength;
	int s2length;
	int64 l;

	if (len < 0) {
		/* The file length overflowed our int64 var, so we can't process this file. */
		sum->count = -1; /* indicate overflow error */
		return;
	}

	if (block_size)
		blength = block_size;
	else if (len <= BLOCK_SIZE * BLOCK_SIZE)
		blength = BLOCK_SIZE;
	else {
		int32 max_blength = protocol_version < 30 ? OLD_MAX_BLOCK_SIZE : MAX_BLOCK_SIZE;
		int32 c;
		int cnt;
		for (c = 1, l = len, cnt = 0; l >>= 2; c <<= 1, cnt++) {}
		if (c < 0 || c >= max_blength)
			blength = max_blength;
		else {
			blength = 0;
			do {
				blength |= c;
				if (len < (int64)blength * blength)
					blength &= ~c;
				c >>= 1;
			} while (c >= 8);	/* round to multiple of 8 */
			blength = MAX(blength, BLOCK_SIZE);
		}
	}

	if (protocol_version < 27) {
		s2length = csum_length;
	} else if (csum_length == SUM_LENGTH) {
		s2length = SUM_LENGTH;
	} else {
		int32 c;
		int b = BLOCKSUM_BIAS;
		for (l = len; l >>= 1; b += 2) {}
		for (c = blength; (c >>= 1) && b; b--) {}
		/* add a bit, subtract rollsum, round up. */
		s2length = (b + 1 - 32 + 7) / 8; /* --optimize in compiler-- */
		s2length = MAX(s2length, csum_length);
		s2length = MIN(s2length, SUM_LENGTH);
	}

	sum->flength	= len;
	sum->blength	= blength;
	sum->s2length	= s2length;
	sum->remainder	= (int32)(len % blength);
	sum->count	= (int32)(l = (len / blength) + (sum->remainder != 0));

	if ((int64)sum->count != l)
		sum->count = -1;

	if (sum->count && DEBUG_GTE(DELTASUM, 2)) {
		rprintf(FINFO,
			"count=%s rem=%ld blength=%ld s2length=%d flength=%s\n",
			big_num(sum->count), (long)sum->remainder, (long)sum->blength,
			sum->s2length, big_num(sum->flength));
	}
}


/*
 * Generate and send a stream of signatures/checksums that describe a buffer
 *
 * Generate approximately one checksum every block_len bytes.
 */
static int generate_and_send_sums(int fd, OFF_T len, int f_out, int f_copy)
{
	int32 i;
	struct map_struct *mapbuf;
	struct sum_struct sum;
	OFF_T offset = 0;

	sum_sizes_sqroot(&sum, len);
	if (sum.count < 0)
		return -1;
	write_sum_head(f_out, &sum);

	if (append_mode > 0 && f_copy < 0)
		return 0;

	if (len > 0)
		mapbuf = map_file(fd, len, MAX_MAP_SIZE, sum.blength);
	else
		mapbuf = NULL;

	for (i = 0; i < sum.count; i++) {
		int32 n1 = (int32)MIN(len, (OFF_T)sum.blength);
		char *map = map_ptr(mapbuf, offset, n1);
		char sum2[MAX_DIGEST_LEN];
		uint32 sum1;

		len -= n1;
		offset += n1;

		if (f_copy >= 0) {
			full_write(f_copy, map, n1);
			if (append_mode > 0)
				continue;
		}

		sum1 = get_checksum1(map, n1);
		get_checksum2(map, n1, sum2);

		if (DEBUG_GTE(DELTASUM, 3)) {
			rprintf(FINFO,
				"chunk[%s] offset=%s len=%ld sum1=%08lx\n",
				big_num(i), big_num(offset - n1), (long)n1,
				(unsigned long)sum1);
		}
		write_int(f_out, sum1);
		write_buf(f_out, sum2, sum.s2length);
	}

	if (mapbuf)
		unmap_file(mapbuf);

	return 0;
}


/* Try to find a filename in the same dir as "fname" with a similar name. */
static struct file_struct *find_fuzzy(struct file_struct *file, struct file_list *dirlist_array[], uchar *fnamecmp_type_ptr)
{
	int fname_len, fname_suf_len;
	const char *fname_suf, *fname = file->basename;
	uint32 lowest_dist = 25 << 16; /* ignore a distance greater than 25 */
	int i, j;
	struct file_struct *lowest_fp = NULL;

	fname_len = strlen(fname);
	fname_suf = find_filename_suffix(fname, fname_len, &fname_suf_len);

	/* Try to find an exact size+mtime match first. */
	for (i = 0; i < fuzzy_basis; i++) {
		struct file_list *dirlist = dirlist_array[i];

		if (!dirlist)
			continue;

		for (j = 0; j < dirlist->used; j++) {
			struct file_struct *fp = dirlist->files[j];

			if (!F_IS_ACTIVE(fp))
				continue;

			if (!S_ISREG(fp->mode) || !F_LENGTH(fp) || fp->flags & FLAG_FILE_SENT)
				continue;

			if (F_LENGTH(fp) == F_LENGTH(file) && same_time(fp->modtime, 0, file->modtime, 0)) {
				if (DEBUG_GTE(FUZZY, 2))
					rprintf(FINFO, "fuzzy size/modtime match for %s\n", f_name(fp, NULL));
				*fnamecmp_type_ptr = FNAMECMP_FUZZY + i;
				return fp;
			}

		}
	}

	for (i = 0; i < fuzzy_basis; i++) {
		struct file_list *dirlist = dirlist_array[i];

		if (!dirlist)
			continue;

		for (j = 0; j < dirlist->used; j++) {
			struct file_struct *fp = dirlist->files[j];
			const char *suf, *name;
			int len, suf_len;
			uint32 dist;

			if (!F_IS_ACTIVE(fp))
				continue;

			if (!S_ISREG(fp->mode) || !F_LENGTH(fp) || fp->flags & FLAG_FILE_SENT)
				continue;

			name = fp->basename;
			len = strlen(name);
			suf = find_filename_suffix(name, len, &suf_len);

			dist = fuzzy_distance(name, len, fname, fname_len, lowest_dist);
			/* Add some extra weight to how well the suffixes match unless we've already disqualified
			 * this file based on a heuristic. */
			if (dist < 0xFFFF0000U) {
				dist += fuzzy_distance(suf, suf_len, fname_suf, fname_suf_len, 0xFFFF0000U) * 10;
			}
			if (DEBUG_GTE(FUZZY, 2)) {
				rprintf(FINFO, "fuzzy distance for %s = %d.%05d\n",
					f_name(fp, NULL), (int)(dist>>16), (int)(dist&0xFFFF));
			}
			if (dist <= lowest_dist) {
				lowest_dist = dist;
				lowest_fp = fp;
				*fnamecmp_type_ptr = FNAMECMP_FUZZY + i;
			}
		}
	}

	return lowest_fp;
}

/* Copy a file found in our --copy-dest handling. */
static int copy_altdest_file(const char *src, const char *dest, struct file_struct *file)
{
	char buf[MAXPATHLEN];
	const char *copy_to, *partialptr;
	int save_preserve_xattrs = preserve_xattrs;
	int ok, fd_w;

	if (inplace) {
		/* Let copy_file open the destination in place. */
		fd_w = -1;
		copy_to = dest;
	} else {
		fd_w = open_tmpfile(buf, dest, file);
		if (fd_w < 0)
			return -1;
		copy_to = buf;
	}
	cleanup_set(copy_to, NULL, NULL, -1, -1);
	if (copy_file(src, copy_to, fd_w, file->mode) < 0) {
		if (INFO_GTE(COPY, 1)) {
			rsyserr(FINFO, errno, "copy_file %s => %s",
				full_fname(src), copy_to);
		}
		/* Try to clean up. */
		unlink(copy_to);
		cleanup_disable();
		return -1;
	}
	partialptr = partial_dir ? partial_dir_fname(dest) : NULL;
	preserve_xattrs = 0; /* xattrs were copied with file */
	ok = finish_transfer(dest, copy_to, src, partialptr, file, 1, 0);
	preserve_xattrs = save_preserve_xattrs;
	cleanup_disable();
	return ok ? 0 : -1;
}

/* This is only called for regular files.  We return -2 if we've finished
 * handling the file, -1 if no dest-linking occurred, or a non-negative
 * value if we found an alternate basis file.  If we're called with the
 * find_exact_for_existing flag, the destination file already exists, so
 * we only try to find an exact alt-dest match.  In this case, the returns
 * are only -2 & -1 (both as above). */
static int try_dests_reg(struct file_struct *file, char *fname, int ndx,
			 char *cmpbuf, stat_x *sxp, int find_exact_for_existing,
			 int itemizing, enum logcode code)
{
	STRUCT_STAT real_st = sxp->st;
	int best_match = -1;
	int match_level = 0;
	int j = 0;

	do {
		pathjoin(cmpbuf, MAXPATHLEN, basis_dir[j], fname);
		if (link_stat(cmpbuf, &sxp->st, 0) < 0 || !S_ISREG(sxp->st.st_mode))
			continue;
		if (match_level == 0) {
			best_match = j;
			match_level = 1;
		}
		if (!quick_check_ok(FT_REG, cmpbuf, file, &sxp->st))
			continue;
		if (match_level == 1) {
			best_match = j;
			match_level = 2;
		}
		if (unchanged_attrs(cmpbuf, file, sxp)) {
			best_match = j;
			match_level = 3;
			break;
		}
		free_stat_x(sxp);
	} while (basis_dir[++j] != NULL);

	if (!match_level)
		goto got_nothing_for_ya;

	if (j != best_match) {
		j = best_match;
		pathjoin(cmpbuf, MAXPATHLEN, basis_dir[j], fname);
		if (link_stat(cmpbuf, &sxp->st, 0) < 0)
			goto got_nothing_for_ya;
	}

	if (match_level == 3 && alt_dest_type != COPY_DEST) {
		if (find_exact_for_existing) {
			if (alt_dest_type == LINK_DEST && real_st.st_dev == sxp->st.st_dev && real_st.st_ino == sxp->st.st_ino)
				return -1;
			if (do_unlink(fname) < 0 && errno != ENOENT)
				goto got_nothing_for_ya;
		}
#ifdef SUPPORT_HARD_LINKS
		if (alt_dest_type == LINK_DEST) {
			if (!hard_link_one(file, fname, cmpbuf, 1))
				goto try_a_copy;
			if (atimes_ndx)
				set_file_attrs(fname, file, sxp, NULL, 0);
			if (preserve_hard_links && F_IS_HLINKED(file))
				finish_hard_link(file, fname, ndx, &sxp->st, itemizing, code, j);
			if (!maybe_ATTRS_REPORT && (INFO_GTE(NAME, 2) || stdout_format_has_i > 1)) {
				itemize(cmpbuf, file, ndx, 1, sxp,
					ITEM_LOCAL_CHANGE | ITEM_XNAME_FOLLOWS,
					0, "");
			}
		} else
#endif
		{
			if (itemizing)
				itemize(cmpbuf, file, ndx, 0, sxp, 0, 0, NULL);
		}
		if (INFO_GTE(NAME, 2) && maybe_ATTRS_REPORT)
			rprintf(FCLIENT, "%s is uptodate\n", fname);
		return -2;
	}

	if (find_exact_for_existing)
		goto got_nothing_for_ya;

	if (match_level >= 2) {
#ifdef SUPPORT_HARD_LINKS
	  try_a_copy: /* Copy the file locally. */
#endif
		if (!dry_run && copy_altdest_file(cmpbuf, fname, file) < 0) {
			if (find_exact_for_existing) /* Can get here via hard-link failure */
				goto got_nothing_for_ya;
			return -1;
		}
		if (itemizing)
			itemize(cmpbuf, file, ndx, 0, sxp, ITEM_LOCAL_CHANGE, 0, NULL);
		if (maybe_ATTRS_REPORT
		 && ((!itemizing && INFO_GTE(NAME, 1) && match_level == 2)
		  || (INFO_GTE(NAME, 2) && match_level == 3))) {
			code = match_level == 3 ? FCLIENT : FINFO;
			rprintf(code, "%s%s\n", fname,
				match_level == 3 ? " is uptodate" : "");
		}
#ifdef SUPPORT_HARD_LINKS
		if (preserve_hard_links && F_IS_HLINKED(file))
			finish_hard_link(file, fname, ndx, &sxp->st, itemizing, code, -1);
#endif
		return -2;
	}

	return FNAMECMP_BASIS_DIR_LOW + j;

got_nothing_for_ya:
	sxp->st = real_st;
	return -1;
}

/* This is only called for non-regular files.  We return -2 if we've finished
 * handling the file, or -1 if no dest-linking occurred, or a non-negative
 * value if we found an alternate basis file. */
static int try_dests_non(struct file_struct *file, char *fname, int ndx,
			 char *cmpbuf, stat_x *sxp, int itemizing,
			 enum logcode code)
{
	int best_match = -1;
	int match_level = 0;
	enum filetype ftype = get_file_type(file->mode);
	int j = 0;

#ifndef SUPPORT_LINKS
	if (ftype == FT_SYMLINK)
		return -1;
#endif
	if (ftype == FT_REG || ftype == FT_UNSUPPORTED) {
		rprintf(FERROR,
			"internal: try_dests_non() called with invalid mode (%o)\n",
			(int)file->mode);
		exit_cleanup(RERR_UNSUPPORTED);
	}

	do {
		pathjoin(cmpbuf, MAXPATHLEN, basis_dir[j], fname);
		if (link_stat(cmpbuf, &sxp->st, 0) < 0)
			continue;
		if (ftype != get_file_type(sxp->st.st_mode))
			continue;
		if (match_level < 1) {
			match_level = 1;
			best_match = j;
		}
		if (!quick_check_ok(ftype, cmpbuf, file, &sxp->st))
			continue;
		if (match_level < 2) {
			match_level = 2;
			best_match = j;
		}
		if (unchanged_attrs(cmpbuf, file, sxp)) {
			match_level = 3;
			best_match = j;
			break;
		}
	} while (basis_dir[++j] != NULL);

	if (!match_level)
		return -1;

	if (j != best_match) {
		j = best_match;
		pathjoin(cmpbuf, MAXPATHLEN, basis_dir[j], fname);
		if (link_stat(cmpbuf, &sxp->st, 0) < 0)
			return -1;
	}

	if (match_level == 3) {
#ifdef SUPPORT_HARD_LINKS
		if (alt_dest_type == LINK_DEST
#ifndef CAN_HARDLINK_SYMLINK
		 && !S_ISLNK(file->mode)
#endif
#ifndef CAN_HARDLINK_SPECIAL
		 && !IS_SPECIAL(file->mode) && !IS_DEVICE(file->mode)
#endif
		 && !S_ISDIR(file->mode)) {
			if (do_link(cmpbuf, fname) < 0) {
				rsyserr(FERROR_XFER, errno,
					"failed to hard-link %s with %s",
					cmpbuf, fname);
				return j;
			}
			if (preserve_hard_links && F_IS_HLINKED(file))
				finish_hard_link(file, fname, ndx, NULL, itemizing, code, -1);
		} else
#endif
			match_level = 2;
		if (itemizing && stdout_format_has_i
		 && (INFO_GTE(NAME, 2) || stdout_format_has_i > 1)) {
			int chg = alt_dest_type == COMPARE_DEST && ftype != FT_DIR ? 0
			    : ITEM_LOCAL_CHANGE + (match_level == 3 ? ITEM_XNAME_FOLLOWS : 0);
			char *lp = match_level == 3 ? "" : NULL;
			itemize(cmpbuf, file, ndx, 0, sxp, chg + ITEM_MATCHED, 0, lp);
		}
		if (INFO_GTE(NAME, 2) && maybe_ATTRS_REPORT) {
			rprintf(FCLIENT, "%s%s is uptodate\n",
				fname, ftype == FT_DIR ? "/" : "");
		}
		return -2;
	}

	return j;
}

static void list_file_entry(struct file_struct *f)
{
	char permbuf[PERMSTRING_SIZE];
	const char *mtime_str = timestring(f->modtime);
	int size_width = human_readable ? 14 : 11;
	int mtime_width = 1 + strlen(mtime_str);
	int atime_width = atimes_ndx ? mtime_width : 0;
	int crtime_width = crtimes_ndx ? mtime_width : 0;

	if (!F_IS_ACTIVE(f)) {
		/* this can happen if duplicate names were removed */
		return;
	}

	/* TODO: indicate '+' if the entry has an ACL. */

	if (missing_args == 2 && f->mode == 0) {
		rprintf(FINFO, "%-*s %s\n",
			10 + 1 + size_width + mtime_width + atime_width + crtime_width, "*missing",
			f_name(f, NULL));
	} else {
		const char *atime_str = atimes_ndx && !S_ISDIR(f->mode) ? timestring(F_ATIME(f)) : "";
		const char *crtime_str = crtimes_ndx ? timestring(F_CRTIME(f)) : "";
		const char *arrow, *lnk;

		permstring(permbuf, f->mode);

#ifdef SUPPORT_LINKS
		if (preserve_links && S_ISLNK(f->mode)) {
			arrow = " -> ";
			lnk = F_SYMLINK(f);
		} else
#endif
			arrow = lnk = "";

		rprintf(FINFO, "%s %*s %s%*s%*s %s%s%s\n",
			permbuf, size_width, human_num(F_LENGTH(f)),
			timestring(f->modtime), atime_width, atime_str, crtime_width, crtime_str,
			f_name(f, NULL), arrow, lnk);
	}
}

static int phase = 0;
static int dflt_perms;

static int implied_dirs_are_missing;
/* Helper for recv_generator's skip_dir and dry_missing_dir tests. */
static BOOL is_below(struct file_struct *file, struct file_struct *subtree)
{
	return F_DEPTH(file) > F_DEPTH(subtree)
		&& (!implied_dirs_are_missing || f_name_has_prefix(file, subtree));
}

/* Acts on the indicated item in cur_flist whose name is fname.  If a dir,
 * make sure it exists, and has the right permissions/timestamp info.  For
 * all other non-regular files (symlinks, etc.) we create them here.  For
 * regular files that have changed, we try to find a basis file and then
 * start sending checksums.  The ndx is the file's unique index value.
 *
 * The fname parameter must point to a MAXPATHLEN buffer!  (e.g it gets
 * passed to delete_item(), which can use it during a recursive delete.)
 *
 * Note that f_out is set to -1 when doing final directory-permission and
 * modification-time repair. */
static void recv_generator(char *fname, struct file_struct *file, int ndx,
			   int itemizing, enum logcode code, int f_out)
{
	static const char *parent_dirname = "";
	static struct file_struct *prior_dir_file = NULL;
	/* Missing dir not created due to --dry-run; will still be scanned. */
	static struct file_struct *dry_missing_dir = NULL;
	/* Missing dir whose contents are skipped altogether due to
	 * --ignore-non-existing, daemon exclude, or mkdir failure. */
	static struct file_struct *skip_dir = NULL;
	static struct file_list *fuzzy_dirlist[MAX_BASIS_DIRS+1];
	static int need_fuzzy_dirlist = 0;
	struct file_struct *fuzzy_file = NULL;
	int fd = -1, f_copy = -1;
	stat_x sx, real_sx;
	STRUCT_STAT partial_st;
	struct file_struct *back_file = NULL;
	int statret, real_ret, stat_errno;
	char *fnamecmp, *partialptr, *backupptr = NULL;
	char fnamecmpbuf[MAXPATHLEN];
	uchar fnamecmp_type;
	int del_opts = delete_mode || force_delete ? DEL_RECURSE : 0;
	enum filetype stype, ftype = get_file_type(file->mode);
	int is_dir = ftype != FT_DIR ? 0
		   : inc_recurse && ndx != cur_flist->ndx_start - 1 ? -1
		   : 1;

	if (DEBUG_GTE(GENR, 1))
		rprintf(FINFO, "recv_generator(%s,%d)\n", fname, ndx);

	if (list_only) {
		if (is_dir < 0
		 || (is_dir && !implied_dirs && file->flags & FLAG_IMPLIED_DIR))
			return;
		list_file_entry(file);
		return;
	}

	maybe_ATTRS_ACCURATE_TIME = always_checksum ? ATTRS_ACCURATE_TIME : 0;

	if (skip_dir) {
		if (is_below(file, skip_dir)) {
			if (is_dir)
				file->flags |= FLAG_MISSING_DIR;
#ifdef SUPPORT_HARD_LINKS
			else if (F_IS_HLINKED(file))
				handle_skipped_hlink(file, itemizing, code, f_out);
#endif
			return;
		}
		skip_dir = NULL;
	}

	init_stat_x(&sx);
	if (daemon_filter_list.head && (*fname != '.' || fname[1])) {
		if (check_filter(&daemon_filter_list, FLOG, fname, is_dir) < 0) {
			if (is_dir < 0)
				return;
#ifdef SUPPORT_HARD_LINKS
			if (F_IS_HLINKED(file))
				handle_skipped_hlink(file, itemizing, code, f_out);
#endif
			rprintf(FERROR_XFER,
				"ERROR: daemon refused to receive %s \"%s\"\n",
				is_dir ? "directory" : "file", fname);
			if (is_dir)
				goto skipping_dir_contents;
			return;
		}
	}

	if (dry_run > 1 || (dry_missing_dir && is_below(file, dry_missing_dir))) {
		int i;
	  parent_is_dry_missing:
		for (i = 0; i < fuzzy_basis; i++) {
			if (fuzzy_dirlist[i]) {
				flist_free(fuzzy_dirlist[i]);
				fuzzy_dirlist[i] = NULL;
			}
		}
		parent_dirname = "";
		statret = -1;
		stat_errno = ENOENT;
	} else {
		const char *dn = file->dirname ? file->dirname : ".";
		dry_missing_dir = NULL;
		if (parent_dirname != dn && strcmp(parent_dirname, dn) != 0) {
			/* Each parent dir must be in the file list or the flist data is bad.
			 * Optimization: most of the time the parent dir will be the last dir
			 * this function was asked to process in the file list. */
			if (!inc_recurse
			 && (*dn != '.' || dn[1]) /* Avoid an issue with --relative and the "." dir. */
			 && (!prior_dir_file || strcmp(dn, f_name(prior_dir_file, NULL)) != 0)) {
				int ok = 0, j = flist_find_name(cur_flist, dn, -1);
				if (j >= 0) {
					struct file_struct *f = cur_flist->sorted[j];
					if (S_ISDIR(f->mode) || (missing_args == 2 && !file->mode && !f->mode))
						ok = 1;
				}
				/* The --delete-missing-args option can actually put invalid entries into
				 * the file list, so if that option was specified, we'll just complain about
				 * it and allow it. */
				if (!ok && missing_args == 2 && file->mode == 0 && j < 0)
					rprintf(FERROR, "WARNING: parent dir is absent in the file list: %s\n", dn);
				else if (!ok) {
					rprintf(FERROR, "ABORTING due to invalid path from sender: %s/%s\n",
						dn, file->basename);
					exit_cleanup(RERR_PROTOCOL);
				}
			}
			if (relative_paths && !implied_dirs && file->mode != 0
			 && do_stat(dn, &sx.st) < 0) {
				if (dry_run)
					goto parent_is_dry_missing;
				if (make_path(fname, MKP_DROP_NAME | MKP_SKIP_SLASH) < 0) {
					rsyserr(FERROR_XFER, errno,
						"recv_generator: mkdir %s failed",
						full_fname(dn));
				}
			}
			if (fuzzy_basis) {
				int i;
				for (i = 0; i < fuzzy_basis; i++) {
					if (fuzzy_dirlist[i]) {
						flist_free(fuzzy_dirlist[i]);
						fuzzy_dirlist[i] = NULL;
					}
				}
				need_fuzzy_dirlist = 1;
			}
#ifdef SUPPORT_ACLS
			if (!preserve_perms)
				dflt_perms = default_perms_for_dir(dn);
#endif
		}
		parent_dirname = dn;

		statret = link_stat(fname, &sx.st, keep_dirlinks && is_dir);
		stat_errno = errno;
	}

	if (missing_args == 2 && file->mode == 0) {
		if (filter_list.head && check_filter(&filter_list, FINFO, fname, is_dir) < 0)
			return;
		if (statret == 0)
			delete_item(fname, sx.st.st_mode, del_opts);
		return;
	}

	if (ignore_non_existing > 0 && statret == -1 && stat_errno == ENOENT) {
		if (is_dir) {
			if (is_dir < 0)
				return;
			skip_dir = file;
			file->flags |= FLAG_MISSING_DIR;
		}
#ifdef SUPPORT_HARD_LINKS
		else if (F_IS_HLINKED(file))
			handle_skipped_hlink(file, itemizing, code, f_out);
#endif
		if (INFO_GTE(SKIP, 1)) {
			rprintf(FINFO, "not creating new %s \"%s\"\n",
				is_dir ? "directory" : "file", fname);
		}
		return;
	}

	if (statret == 0 && !(sx.st.st_mode & S_IWUSR)
	 && !am_root && sx.st.st_uid == our_uid)
		del_opts |= DEL_NO_UID_WRITE;

	if (statret == 0)
		stype = get_file_type(sx.st.st_mode);
	else
		stype = FT_UNSUPPORTED;

	if (ignore_existing > 0 && statret == 0
	 && (!is_dir || stype != FT_DIR)) {
		if (INFO_GTE(SKIP, 1) && is_dir >= 0) {
			const char *suf = "";
			if (INFO_GTE(SKIP, 2)) {
				if (ftype != stype)
					suf = " (type change)";
				else if (!quick_check_ok(ftype, fname, file, &sx.st))
					suf = always_checksum ? " (sum change)" : " (file change)";
				else if (!unchanged_attrs(fname, file, &sx))
					suf = " (attr change)";
				else
					suf = " (uptodate)";
			}
			rprintf(FINFO, "%s exists%s\n", fname, suf);
		}
#ifdef SUPPORT_HARD_LINKS
		if (F_IS_HLINKED(file))
			handle_skipped_hlink(file, itemizing, code, f_out);
#endif
		goto cleanup;
	}

	fnamecmp = fname;

	if (is_dir) {
		mode_t added_perms;
		if (!implied_dirs && file->flags & FLAG_IMPLIED_DIR)
			goto cleanup;
		if (am_root < 0) {
			/* For --fake-super, the dir must be useable by the copying
			 * user, just like it would be for root. */
			added_perms = S_IRUSR|S_IWUSR|S_IXUSR;
		} else
			added_perms = 0;
		if (is_dir < 0) {
			if (!preserve_mtimes || omit_dir_times)
				goto cleanup;
			/* In inc_recurse mode we want to make sure any missing
			 * directories get created while we're still processing
			 * the parent dir (which allows us to touch the parent
			 * dir's mtime right away).  We will handle the dir in
			 * full later (right before we handle its contents). */
			if (statret == 0
			 && (stype == FT_DIR
			  || delete_item(fname, sx.st.st_mode, del_opts | DEL_FOR_DIR) != 0))
				goto cleanup; /* Any errors get reported later. */
			if (do_mkdir(fname, (file->mode|added_perms) & 0700) == 0)
				file->flags |= FLAG_DIR_CREATED;
			goto cleanup;
		}
		/* The file to be received is a directory, so we need
		 * to prepare appropriately.  If there is already a
		 * file of that name and it is *not* a directory, then
		 * we need to delete it.  If it doesn't exist, then
		 * (perhaps recursively) create it. */
		if (statret == 0 && stype != FT_DIR) {
			if (delete_item(fname, sx.st.st_mode, del_opts | DEL_FOR_DIR) != 0)
				goto skipping_dir_contents;
			statret = -1;
		}
		if (dry_run && statret != 0) {
			if (!dry_missing_dir)
				dry_missing_dir = file;
			file->flags |= FLAG_MISSING_DIR;
		}
		init_stat_x(&real_sx);
		real_sx.st = sx.st;
		real_ret = statret;
		if (file->flags & FLAG_DIR_CREATED)
			statret = -1;
		if (!preserve_perms) { /* See comment in non-dir code below. */
			file->mode = dest_mode(file->mode, sx.st.st_mode, dflt_perms, statret == 0);
		}
		if (statret != 0 && basis_dir[0] != NULL) {
			int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx, itemizing, code);
			if (j == -2) {
				itemizing = 0;
				code = FNONE;
				statret = 1;
			} else if (j >= 0) {
				statret = 1;
				fnamecmp = fnamecmpbuf;
			}
		}
		if (itemizing && f_out != -1) {
			itemize(fnamecmp, file, ndx, statret, &sx,
				statret ? ITEM_LOCAL_CHANGE : 0, 0, NULL);
		}
		if (real_ret != 0 && do_mkdir(fname,file->mode|added_perms) < 0 && errno != EEXIST) {
			if (!relative_paths || errno != ENOENT
			 || make_path(fname, MKP_DROP_NAME | MKP_SKIP_SLASH) < 0
			 || (do_mkdir(fname, file->mode|added_perms) < 0 && errno != EEXIST)) {
				rsyserr(FERROR_XFER, errno,
					"recv_generator: mkdir %s failed",
					full_fname(fname));
			  skipping_dir_contents:
				rprintf(FERROR, "*** Skipping any contents from this failed directory ***\n");
				skip_dir = file;
				file->flags |= FLAG_MISSING_DIR;
				goto cleanup;
			}
		}

#ifdef SUPPORT_XATTRS
		if (preserve_xattrs && statret == 1)
			copy_xattrs(fnamecmpbuf, fname);
#endif
		if (set_file_attrs(fname, file, real_ret ? NULL : &real_sx, NULL, 0)
		 && INFO_GTE(NAME, 1) && code != FNONE && f_out != -1)
			rprintf(code, "%s/\n", fname);

		/* We need to ensure that the dirs in the transfer have both
		 * readable and writable permissions during the time we are
		 * putting files within them.  This is then restored to the
		 * former permissions after the transfer is done. */
#ifdef HAVE_CHMOD
		if (!am_root && (file->mode & S_IRWXU) != S_IRWXU && dir_tweaking) {
			mode_t mode = file->mode | S_IRWXU;
			if (do_chmod(fname, mode) < 0) {
				rsyserr(FERROR_XFER, errno,
					"failed to modify permissions on %s",
					full_fname(fname));
			}
			need_retouch_dir_perms = 1;
		}
#endif

		if (real_ret != 0 && one_file_system)
			real_sx.st.st_dev = filesystem_dev;
		if (inc_recurse) {
			if (one_file_system) {
				uint32 *devp = F_DIR_DEV_P(file);
				DEV_MAJOR(devp) = major(real_sx.st.st_dev);
				DEV_MINOR(devp) = minor(real_sx.st.st_dev);
			}
		}
		else if (delete_during && f_out != -1 && !phase
		    && !(file->flags & FLAG_MISSING_DIR)) {
			if (file->flags & FLAG_CONTENT_DIR)
				delete_in_dir(fname, file, real_sx.st.st_dev);
			else
				change_local_filter_dir(fname, strlen(fname), F_DEPTH(file));
		}
		prior_dir_file = file;
		goto cleanup;
	}

	/* If we're not preserving permissions, change the file-list's
	 * mode based on the local permissions and some heuristics. */
	if (!preserve_perms) {
		int exists = statret == 0 && stype != FT_DIR;
		file->mode = dest_mode(file->mode, sx.st.st_mode, dflt_perms, exists);
	}

#ifdef SUPPORT_HARD_LINKS
	if (preserve_hard_links && F_HLINK_NOT_FIRST(file)
	 && hard_link_check(file, ndx, fname, statret, &sx, itemizing, code))
		goto cleanup;
#endif

	if (preserve_links && ftype == FT_SYMLINK) {
#ifdef SUPPORT_LINKS
		const char *sl = F_SYMLINK(file);
		if (safe_symlinks && unsafe_symlink(sl, fname)) {
			if (INFO_GTE(NAME, 1)) {
				if (solo_file) {
					/* fname contains the destination path, but we
					 * want to report the source path. */
					fname = f_name(file, NULL);
				}
				rprintf(FINFO,
					"ignoring unsafe symlink \"%s\" -> \"%s\"\n",
					fname, sl);
			}
			goto cleanup;
		}
		if (statret == 0) {
			if (stype == FT_SYMLINK && quick_check_ok(stype, fname, file, &sx.st)) {
				/* The link is pointing to the right place. */
				set_file_attrs(fname, file, &sx, NULL, maybe_ATTRS_REPORT);
				if (itemizing)
					itemize(fname, file, ndx, 0, &sx, 0, 0, NULL);
#ifdef SUPPORT_HARD_LINKS
				if (preserve_hard_links && F_IS_HLINKED(file))
					finish_hard_link(file, fname, ndx, &sx.st, itemizing, code, -1);
#endif
				if (remove_source_files == 1)
					goto return_with_success;
				goto cleanup;
			}
		} else if (basis_dir[0] != NULL) {
			int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx, itemizing, code);
			if (j == -2) {
#ifndef CAN_HARDLINK_SYMLINK
				if (alt_dest_type == LINK_DEST) {
					/* Resort to --copy-dest behavior. */
				} else
#endif
				if (alt_dest_type != COPY_DEST)
					goto cleanup;
				itemizing = 0;
				code = FNONE;
			} else if (j >= 0) {
				statret = 1;
				fnamecmp = fnamecmpbuf;
			}
		}
		if (atomic_create(file, fname, sl, NULL, MAKEDEV(0, 0), &sx, statret == 0 ? DEL_FOR_SYMLINK : 0)) {
			set_file_attrs(fname, file, NULL, NULL, 0);
			if (itemizing) {
				if (statret == 0 && stype != FT_SYMLINK)
					statret = -1;
				itemize(fnamecmp, file, ndx, statret, &sx,
					ITEM_LOCAL_CHANGE|ITEM_REPORT_CHANGE, 0, NULL);
			}
			if (code != FNONE && INFO_GTE(NAME, 1))
				rprintf(code, "%s -> %s\n", fname, sl);
#ifdef SUPPORT_HARD_LINKS
			if (preserve_hard_links && F_IS_HLINKED(file))
				finish_hard_link(file, fname, ndx, NULL, itemizing, code, -1);
#endif
			/* This does not check remove_source_files == 1
			 * because this is one of the items that the old
			 * --remove-sent-files option would remove. */
			if (remove_source_files)
				goto return_with_success;
		}
#endif
		goto cleanup;
	}

	if ((am_root && preserve_devices && ftype == FT_DEVICE)
	 || (preserve_specials && ftype == FT_SPECIAL)) {
		dev_t rdev;
		int del_for_flag;
		if (ftype == FT_DEVICE) {
			uint32 *devp = F_RDEV_P(file);
			rdev = MAKEDEV(DEV_MAJOR(devp), DEV_MINOR(devp));
			del_for_flag = DEL_FOR_DEVICE;
		} else {
			rdev = 0;
			del_for_flag = DEL_FOR_SPECIAL;
		}
		if (statret == 0) {
			if (ftype != stype)
				statret = -1;
			else if (quick_check_ok(ftype, fname, file, &sx.st)) {
				/* The device or special file is identical. */
				set_file_attrs(fname, file, &sx, NULL, maybe_ATTRS_REPORT);
				if (itemizing)
					itemize(fname, file, ndx, 0, &sx, 0, 0, NULL);
#ifdef SUPPORT_HARD_LINKS
				if (preserve_hard_links && F_IS_HLINKED(file))
					finish_hard_link(file, fname, ndx, &sx.st, itemizing, code, -1);
#endif
				if (remove_source_files == 1)
					goto return_with_success;
				goto cleanup;
			}
		} else if (basis_dir[0] != NULL) {
			int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx, itemizing, code);
			if (j == -2) {
#ifndef CAN_HARDLINK_SPECIAL
				if (alt_dest_type == LINK_DEST) {
					/* Resort to --copy-dest behavior. */
				} else
#endif
				if (alt_dest_type != COPY_DEST)
					goto cleanup;
				itemizing = 0;
				code = FNONE;
			} else if (j >= 0) {
				statret = 1;
				fnamecmp = fnamecmpbuf;
			}
		}
		if (DEBUG_GTE(GENR, 1)) {
			rprintf(FINFO, "mknod(%s, 0%o, [%ld,%ld])\n",
				fname, (int)file->mode,
				(long)major(rdev), (long)minor(rdev));
		}
		if (atomic_create(file, fname, NULL, NULL, rdev, &sx, del_for_flag)) {
			set_file_attrs(fname, file, NULL, NULL, 0);
			if (itemizing) {
				itemize(fnamecmp, file, ndx, statret, &sx,
					ITEM_LOCAL_CHANGE|ITEM_REPORT_CHANGE, 0, NULL);
			}
			if (code != FNONE && INFO_GTE(NAME, 1))
				rprintf(code, "%s\n", fname);
#ifdef SUPPORT_HARD_LINKS
			if (preserve_hard_links && F_IS_HLINKED(file))
				finish_hard_link(file, fname, ndx, NULL, itemizing, code, -1);
#endif
			if (remove_source_files == 1)
				goto return_with_success;
		}
		goto cleanup;
	}

	if (ftype != FT_REG) {
		if (INFO_GTE(NONREG, 1)) {
			if (solo_file)
				fname = f_name(file, NULL);
			rprintf(FINFO, "skipping non-regular file \"%s\"\n", fname);
		}
		goto cleanup;
	}

	if (max_size >= 0 && F_LENGTH(file) > max_size) {
		if (INFO_GTE(SKIP, 1)) {
			if (solo_file)
				fname = f_name(file, NULL);
			rprintf(FINFO, "%s is over max-size\n", fname);
		}
		goto cleanup;
	}
	if (min_size >= 0 && F_LENGTH(file) < min_size) {
		if (INFO_GTE(SKIP, 1)) {
			if (solo_file)
				fname = f_name(file, NULL);
			rprintf(FINFO, "%s is under min-size\n", fname);
		}
		goto cleanup;
	}

	if (update_only > 0 && statret == 0 && file->modtime - sx.st.st_mtime < modify_window) {
		if (INFO_GTE(SKIP, 1))
			rprintf(FINFO, "%s is newer\n", fname);
#ifdef SUPPORT_HARD_LINKS
		if (F_IS_HLINKED(file))
			handle_skipped_hlink(file, itemizing, code, f_out);
#endif
		goto cleanup;
	}

	fnamecmp_type = FNAMECMP_FNAME;

	if (statret == 0 && !(stype == FT_REG || (write_devices && stype == FT_DEVICE))) {
		if (delete_item(fname, sx.st.st_mode, del_opts | DEL_FOR_FILE) != 0)
			goto cleanup;
		statret = -1;
		stat_errno = ENOENT;
	}

	if (basis_dir[0] != NULL && (statret != 0 || alt_dest_type != COPY_DEST)) {
		int j = try_dests_reg(file, fname, ndx, fnamecmpbuf, &sx, statret == 0, itemizing, code);
		if (j == -2) {
			if (remove_source_files == 1)
				goto return_with_success;
			goto cleanup;
		}
		if (j >= 0) {
			fnamecmp = fnamecmpbuf;
			fnamecmp_type = j;
			statret = 0;
		}
	}

	init_stat_x(&real_sx);
	real_sx.st = sx.st; /* Don't copy xattr/acl pointers, as they would free wrong. */
	real_ret = statret;

	if (partial_dir && (partialptr = partial_dir_fname(fname)) != NULL
	 && link_stat(partialptr, &partial_st, 0) == 0
	 && S_ISREG(partial_st.st_mode)) {
		if (statret != 0)
			goto prepare_to_open;
	} else
		partialptr = NULL;

	if (statret != 0 && fuzzy_basis) {
		if (need_fuzzy_dirlist) {
			const char *dn = file->dirname ? file->dirname : ".";
			int i;
			strlcpy(fnamecmpbuf, dn, sizeof fnamecmpbuf);
			for (i = 0; i < fuzzy_basis; i++) {
				if (i && pathjoin(fnamecmpbuf, MAXPATHLEN, basis_dir[i-1], dn) >= MAXPATHLEN)
					continue;
				fuzzy_dirlist[i] = get_dirlist(fnamecmpbuf, -1, GDL_IGNORE_FILTER_RULES | GDL_PERHAPS_DIR);
				if (fuzzy_dirlist[i] && fuzzy_dirlist[i]->used == 0) {
					flist_free(fuzzy_dirlist[i]);
					fuzzy_dirlist[i] = NULL;
				}
			}
			need_fuzzy_dirlist = 0;
		}

		/* Sets fnamecmp_type to FNAMECMP_FUZZY or above. */
		fuzzy_file = find_fuzzy(file, fuzzy_dirlist, &fnamecmp_type);
		if (fuzzy_file) {
			f_name(fuzzy_file, fnamecmpbuf);
			if (DEBUG_GTE(FUZZY, 1)) {
				rprintf(FINFO, "fuzzy basis selected for %s: %s\n",
					fname, fnamecmpbuf);
			}
			sx.st.st_size = F_LENGTH(fuzzy_file);
			statret = 0;
			fnamecmp = fnamecmpbuf;
		}
	}

	if (statret != 0) {
#ifdef SUPPORT_HARD_LINKS
		if (preserve_hard_links && F_HLINK_NOT_LAST(file)) {
			cur_flist->in_progress++;
			goto cleanup;
		}
#endif
		if (stat_errno == ENOENT)
			goto notify_others;
		rsyserr(FERROR_XFER, stat_errno, "recv_generator: failed to stat %s",
			full_fname(fname));
		goto cleanup;
	}

	if (write_devices && IS_DEVICE(sx.st.st_mode) && sx.st.st_size == 0) {
		/* This early open into fd skips the regular open below. */
		if ((fd = do_open_nofollow(fnamecmp, O_RDONLY)) >= 0)
			real_sx.st.st_size = sx.st.st_size = get_device_size(fd, fnamecmp);
	}

	if (fnamecmp_type <= FNAMECMP_BASIS_DIR_HIGH)
		;
	else if (fnamecmp_type >= FNAMECMP_FUZZY)
		;
	else if (quick_check_ok(FT_REG, fnamecmp, file, &sx.st)) {
		if (partialptr) {
			do_unlink(partialptr);
			handle_partial_dir(partialptr, PDIR_DELETE);
		}
		set_file_attrs(fname, file, &sx, NULL, maybe_ATTRS_REPORT | maybe_ATTRS_ACCURATE_TIME);
		if (itemizing)
			itemize(fnamecmp, file, ndx, statret, &sx, 0, 0, NULL);
#ifdef SUPPORT_HARD_LINKS
		if (preserve_hard_links && F_IS_HLINKED(file))
			finish_hard_link(file, fname, ndx, &sx.st, itemizing, code, -1);
#endif
		if (remove_source_files != 1)
			goto cleanup;
	  return_with_success:
		if (!dry_run)
			send_msg_success(fname, ndx);
		goto cleanup;
	}

	if (append_mode > 0 && sx.st.st_size >= F_LENGTH(file)) {
#ifdef SUPPORT_HARD_LINKS
		if (F_IS_HLINKED(file))
			handle_skipped_hlink(file, itemizing, code, f_out);
#endif
		goto cleanup;
	}

  prepare_to_open:
	if (partialptr) {
		sx.st = partial_st;
		fnamecmp = partialptr;
		fnamecmp_type = FNAMECMP_PARTIAL_DIR;
		statret = 0;
	}

	if (!do_xfers)
		goto notify_others;

	if (read_batch || whole_file) {
		if (inplace && make_backups > 0 && fnamecmp_type == FNAMECMP_FNAME) {
			if (!(backupptr = get_backup_name(fname)))
				goto cleanup;
			if (!(back_file = make_file(fname, NULL, NULL, 0, NO_FILTERS)))
				goto pretend_missing;
			if (copy_file(fname, backupptr, -1, back_file->mode) < 0) {
				unmake_file(back_file);
				back_file = NULL;
				goto cleanup;
			}
		}
		goto notify_others;
	}

	if (fuzzy_dirlist[0]) {
		int j = flist_find(fuzzy_dirlist[0], file);
		if (j >= 0) /* don't use changing file as future fuzzy basis */
			fuzzy_dirlist[0]->files[j]->flags |= FLAG_FILE_SENT;
	}

	/* open the file */
	if (fd < 0 && (fd = do_open_checklinks(fnamecmp)) < 0) {
		rsyserr(FERROR, errno, "failed to open %s, continuing",
			full_fname(fnamecmp));
	  pretend_missing:
		/* pretend the file didn't exist */
#ifdef SUPPORT_HARD_LINKS
		if (preserve_hard_links && F_HLINK_NOT_LAST(file)) {
			cur_flist->in_progress++;
			goto cleanup;
		}
#endif
		statret = real_ret = -1;
		goto notify_others;
	}

	if (inplace && make_backups > 0 && fnamecmp_type == FNAMECMP_FNAME) {
		if (!(backupptr = get_backup_name(fname))) {
			goto cleanup;
		}
		if (!(back_file = make_file(fname, NULL, NULL, 0, NO_FILTERS))) {
			goto pretend_missing;
		}
		if (robust_unlink(backupptr) && errno != ENOENT) {
			rsyserr(FERROR_XFER, errno, "unlink %s",
				full_fname(backupptr));
			unmake_file(back_file);
			back_file = NULL;
			goto cleanup;
		}
		if ((f_copy = do_open(backupptr, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600)) < 0) {
			rsyserr(FERROR_XFER, errno, "open %s", full_fname(backupptr));
			unmake_file(back_file);
			back_file = NULL;
			goto cleanup;
		}
		fnamecmp_type = FNAMECMP_BACKUP;
	}

	if (DEBUG_GTE(DELTASUM, 3)) {
		rprintf(FINFO, "gen mapped %s of size %s\n",
			fnamecmp, big_num(sx.st.st_size));
	}

	if (DEBUG_GTE(DELTASUM, 2))
		rprintf(FINFO, "generating and sending sums for %d\n", ndx);

  notify_others:
	if (remove_source_files && !delay_updates && !phase && !dry_run)
		increment_active_files(ndx, itemizing, code);
	if (inc_recurse && (!dry_run || write_batch < 0))
		cur_flist->in_progress++;
#ifdef SUPPORT_HARD_LINKS
	if (preserve_hard_links && F_IS_HLINKED(file))
		file->flags |= FLAG_FILE_SENT;
#endif
	write_ndx(f_out, ndx);
	if (itemizing) {
		int iflags = ITEM_TRANSFER;
		if (always_checksum > 0)
			iflags |= ITEM_REPORT_CHANGE;
		if (fnamecmp_type != FNAMECMP_FNAME)
			iflags |= ITEM_BASIS_TYPE_FOLLOWS;
		if (fnamecmp_type >= FNAMECMP_FUZZY)
			iflags |= ITEM_XNAME_FOLLOWS;
		itemize(fnamecmp, file, -1, real_ret, &real_sx, iflags, fnamecmp_type,
			fuzzy_file ? fuzzy_file->basename : NULL);
		free_stat_x(&real_sx);
	}

	if (!do_xfers) {
#ifdef SUPPORT_HARD_LINKS
		if (preserve_hard_links && F_IS_HLINKED(file))
			finish_hard_link(file, fname, ndx, &sx.st, itemizing, code, -1);
#endif
		goto cleanup;
	}
	if (read_batch)
		goto cleanup;

	if (statret != 0 || whole_file)
		write_sum_head(f_out, NULL);
	else if (sx.st.st_size <= 0) {
		write_sum_head(f_out, NULL);
	} else {
		if (generate_and_send_sums(fd, sx.st.st_size, f_out, f_copy) < 0) {
			rprintf(FWARNING,
				"WARNING: file is too large for checksum sending: %s\n",
				fnamecmp);
			write_sum_head(f_out, NULL);
		}
	}

  cleanup:
	if (fd >= 0)
		close(fd);
	if (back_file) {
		int save_preserve_xattrs = preserve_xattrs;
		if (f_copy >= 0)
			close(f_copy);
#ifdef SUPPORT_XATTRS
		if (preserve_xattrs) {
			copy_xattrs(fname, backupptr);
			preserve_xattrs = 0;
		}
#endif
		set_file_attrs(backupptr, back_file, NULL, NULL, 0);
		preserve_xattrs = save_preserve_xattrs;
		if (INFO_GTE(BACKUP, 1)) {
			rprintf(FINFO, "backed up %s to %s\n",
				fname, backupptr);
		}
		unmake_file(back_file);
	}

	free_stat_x(&sx);
}

/* If we are replacing an existing hard link, symlink, device, or special file,
 * create a temp-name item and rename it into place.  A symlimk specifies slnk,
 * a hard link specifies hlnk, otherwise we create a device based on rdev.
 * Specify 0 for the del_for_flag if there is not a file to replace.  This
 * returns 1 on success and 0 on failure. */
int atomic_create(struct file_struct *file, char *fname, const char *slnk, const char *hlnk,
		  dev_t rdev, stat_x *sxp, int del_for_flag)
{
	char tmpname[MAXPATHLEN];
	const char *create_name;
	int skip_atomic, dir_in_the_way = del_for_flag && S_ISDIR(sxp->st.st_mode);

	if (!del_for_flag || dir_in_the_way || tmpdir || !get_tmpname(tmpname, fname, True))
		skip_atomic = 1;
	else
		skip_atomic = 0;

	if (del_for_flag) {
		if (make_backups > 0 && !dir_in_the_way) {
			if (!make_backup(fname, skip_atomic))
				return 0;
		} else if (skip_atomic) {
			int del_opts = delete_mode || force_delete ? DEL_RECURSE : 0;
			if (delete_item(fname, sxp->st.st_mode, del_opts | del_for_flag) != 0)
				return 0;
		}
	}

	create_name = skip_atomic ? fname : tmpname;

	if (slnk) {
#ifdef SUPPORT_LINKS
		if (do_symlink(slnk, create_name) < 0) {
			rsyserr(FERROR_XFER, errno, "symlink %s -> \"%s\" failed",
				full_fname(create_name), slnk);
			return 0;
		}
#else
		return 0;
#endif
	} else if (hlnk) {
#ifdef SUPPORT_HARD_LINKS
		if (!hard_link_one(file, create_name, hlnk, 0))
			return 0;
#else
		return 0;
#endif
	} else {
		if (do_mknod(create_name, file->mode, rdev) < 0) {
			rsyserr(FERROR_XFER, errno, "mknod %s failed",
				full_fname(create_name));
			return 0;
		}
	}

	if (!skip_atomic) {
		if (do_rename(tmpname, fname) < 0) {
			char *full_tmpname = strdup(full_fname(tmpname));
			if (full_tmpname == NULL)
				out_of_memory("atomic_create");
			rsyserr(FERROR_XFER, errno, "rename %s -> \"%s\" failed",
				full_tmpname, full_fname(fname));
			free(full_tmpname);
			do_unlink(tmpname);
			return 0;
		}
	}

	return 1;
}

#ifdef SUPPORT_HARD_LINKS
static void handle_skipped_hlink(struct file_struct *file, int itemizing,
				 enum logcode code, int f_out)
{
	char fbuf[MAXPATHLEN];
	int new_last_ndx;
	struct file_list *save_flist = cur_flist;

	/* If we skip the last item in a chain of links and there was a
	 * prior non-skipped hard-link waiting to finish, finish it now. */
	if ((new_last_ndx = skip_hard_link(file, &cur_flist)) < 0)
		return;

	file = cur_flist->files[new_last_ndx - cur_flist->ndx_start];
	cur_flist->in_progress--; /* undo prior increment */
	f_name(file, fbuf);
	recv_generator(fbuf, file, new_last_ndx, itemizing, code, f_out);

	cur_flist = save_flist;
}
#endif

static void touch_up_dirs(struct file_list *flist, int ndx)
{
	static int counter = 0;
	struct file_struct *file;
	char *fname;
	BOOL fix_dir_perms;
	int i, start, end;

	if (ndx < 0) {
		start = 0;
		end = flist->used - 1;
	} else
		start = end = ndx;

	/* Fix any directory permissions that were modified during the
	 * transfer and/or re-set any tweaked modified-time values. */
	for (i = start; i <= end; i++, counter++) {
		file = flist->files[i];
		if (!F_IS_ACTIVE(file))
			continue;
		if (!S_ISDIR(file->mode)
		 || (!implied_dirs && file->flags & FLAG_IMPLIED_DIR))
			continue;
		if (DEBUG_GTE(TIME, 2)) {
			fname = f_name(file, NULL);
			rprintf(FINFO, "touch_up_dirs: %s (%d)\n",
				NS(fname), i);
		}
		/* Be sure not to retouch permissions with --fake-super. */
		fix_dir_perms = !am_root && !(file->mode & S_IWUSR);
		if (file->flags & FLAG_MISSING_DIR || !(need_retouch_dir_times || fix_dir_perms))
			continue;
		fname = f_name(file, NULL);
		if (fix_dir_perms)
			do_chmod(fname, file->mode);
		if (need_retouch_dir_times) {
			STRUCT_STAT st;
			if (link_stat(fname, &st, 0) == 0 && mtime_differs(&st, file)) {
				st.st_mtime = file->modtime;
#ifdef ST_MTIME_NSEC
				st.ST_MTIME_NSEC = F_MOD_NSEC_or_0(file);
#endif
				set_times(fname, &st);
			}
		}
		if (counter >= loopchk_limit) {
			if (allowed_lull)
				maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH);
			else
				maybe_flush_socket(0);
			counter = 0;
		}
	}
}

void check_for_finished_files(int itemizing, enum logcode code, int check_redo)
{
	struct file_struct *file;
	struct file_list *flist;
	char fbuf[MAXPATHLEN];
	int ndx;

	while (1) {
#ifdef SUPPORT_HARD_LINKS
		if (preserve_hard_links && (ndx = get_hlink_num()) != -1) {
			int send_failed = (ndx == -2);
			if (send_failed)
				ndx = get_hlink_num();
			flist = flist_for_ndx(ndx, "check_for_finished_files.1");
			file = flist->files[ndx - flist->ndx_start];
			assert(file->flags & FLAG_HLINKED);
			if (send_failed)
				handle_skipped_hlink(file, itemizing, code, sock_f_out);
			else
				finish_hard_link(file, f_name(file, fbuf), ndx, NULL, itemizing, code, -1);
			flist->in_progress--;
			continue;
		}
#endif

		if (check_redo && (ndx = get_redo_num()) != -1) {
			OFF_T save_max_size = max_size;
			OFF_T save_min_size = min_size;
			csum_length = SUM_LENGTH;
			max_size = -1;
			min_size = -1;
			ignore_existing = -ignore_existing;
			ignore_non_existing = -ignore_non_existing;
			update_only = -update_only;
			always_checksum = -always_checksum;
			size_only = -size_only;
			append_mode = -append_mode;
			make_backups = -make_backups; /* avoid dup backup w/inplace */
			ignore_times++;

			flist = cur_flist;
			cur_flist = flist_for_ndx(ndx, "check_for_finished_files.2");

			file = cur_flist->files[ndx - cur_flist->ndx_start];
			if (solo_file)
				strlcpy(fbuf, solo_file, sizeof fbuf);
			else
				f_name(file, fbuf);
			recv_generator(fbuf, file, ndx, itemizing, code, sock_f_out);
			cur_flist->to_redo--;

			cur_flist = flist;

			csum_length = SHORT_SUM_LENGTH;
			max_size = save_max_size;
			min_size = save_min_size;
			ignore_existing = -ignore_existing;
			ignore_non_existing = -ignore_non_existing;
			update_only = -update_only;
			always_checksum = -always_checksum;
			size_only = -size_only;
			append_mode = -append_mode;
			make_backups = -make_backups;
			ignore_times--;
			continue;
		}

		if (cur_flist == first_flist)
			break;

		/* We only get here if inc_recurse is enabled. */
		if (first_flist->in_progress || first_flist->to_redo)
			break;

		write_ndx(sock_f_out, NDX_DONE);
		if (!read_batch && !flist_eof) {
			int old_total = 0;
			for (flist = first_flist; flist != cur_flist; flist = flist->next)
				old_total += flist->used;
			maybe_flush_socket(!flist_eof && file_total - old_total < MIN_FILECNT_LOOKAHEAD/2);
		}

		if (delete_during == 2 || !dir_tweaking) {
			/* Skip directory touch-up. */
		} else if (first_flist->parent_ndx >= 0)
			touch_up_dirs(dir_flist, first_flist->parent_ndx);

		flist_free(first_flist); /* updates first_flist */
	}
}

void generate_files(int f_out, const char *local_name)
{
	int i, ndx, next_loopchk = 0;
	char fbuf[MAXPATHLEN];
	int itemizing;
	enum logcode code;
	int save_info_flist = info_levels[INFO_FLIST];
	int save_info_progress = info_levels[INFO_PROGRESS];

	if (protocol_version >= 29) {
		itemizing = 1;
		maybe_ATTRS_REPORT = stdout_format_has_i ? 0 : ATTRS_REPORT;
		code = logfile_format_has_i ? FNONE : FLOG;
	} else if (am_daemon) {
		itemizing = logfile_format_has_i && do_xfers;
		maybe_ATTRS_REPORT = ATTRS_REPORT;
		code = itemizing || !do_xfers ? FCLIENT : FINFO;
	} else if (!am_server) {
		itemizing = stdout_format_has_i;
		maybe_ATTRS_REPORT = stdout_format_has_i ? 0 : ATTRS_REPORT;
		code = itemizing ? FNONE : FINFO;
	} else {
		itemizing = 0;
		maybe_ATTRS_REPORT = ATTRS_REPORT;
		code = FINFO;
	}
	solo_file = local_name;
	dir_tweaking = !(list_only || solo_file || dry_run);
	need_retouch_dir_times = preserve_mtimes && !omit_dir_times;
	loopchk_limit = allowed_lull ? allowed_lull * 5 : 200;
	symlink_timeset_failed_flags = ITEM_REPORT_TIME
	    | (protocol_version >= 30 || !am_server ? ITEM_REPORT_TIMEFAIL : 0);
	implied_dirs_are_missing = relative_paths && !implied_dirs && protocol_version < 30;

	if (DEBUG_GTE(GENR, 1))
		rprintf(FINFO, "generator starting pid=%d\n", (int)getpid());

	if (delete_before && !solo_file && cur_flist->used > 0)
		do_delete_pass();
	if (delete_during == 2) {
		deldelay_size = BIGPATHBUFLEN * 4;
		deldelay_buf = new_array(char, deldelay_size);
	}
	info_levels[INFO_FLIST] = info_levels[INFO_PROGRESS] = 0;

	if (append_mode > 0 || whole_file < 0)
		whole_file = 0;
	if (DEBUG_GTE(FLIST, 1)) {
		rprintf(FINFO, "delta-transmission %s\n",
			whole_file
			? "disabled for local transfer or --whole-file"
			: "enabled");
	}

	dflt_perms = (ACCESSPERMS & ~orig_umask);

	do {
#ifdef SUPPORT_HARD_LINKS
		if (preserve_hard_links && inc_recurse) {
			while (!flist_eof && file_total < MIN_FILECNT_LOOKAHEAD/2)
				wait_for_receiver();
		}
#endif

		if (inc_recurse && cur_flist->parent_ndx >= 0) {
			struct file_struct *fp = dir_flist->files[cur_flist->parent_ndx];
			if (solo_file)
				strlcpy(fbuf, solo_file, sizeof fbuf);
			else
				f_name(fp, fbuf);
			ndx = cur_flist->ndx_start - 1;
			recv_generator(fbuf, fp, ndx, itemizing, code, f_out);
			if (delete_during && dry_run < 2 && !list_only
			 && !(fp->flags & FLAG_MISSING_DIR)) {
				if (fp->flags & FLAG_CONTENT_DIR) {
					dev_t dirdev;
					if (one_file_system) {
						uint32 *devp = F_DIR_DEV_P(fp);
						dirdev = MAKEDEV(DEV_MAJOR(devp), DEV_MINOR(devp));
					} else
						dirdev = MAKEDEV(0, 0);
					delete_in_dir(fbuf, fp, dirdev);
				} else
					change_local_filter_dir(fbuf, strlen(fbuf), F_DEPTH(fp));
			}
		}
		for (i = cur_flist->low; i <= cur_flist->high; i++) {
			struct file_struct *file = cur_flist->sorted[i];

			if (!F_IS_ACTIVE(file))
				continue;

			if (unsort_ndx)
				ndx = F_NDX(file);
			else
				ndx = i + cur_flist->ndx_start;

			if (solo_file)
				strlcpy(fbuf, solo_file, sizeof fbuf);
			else
				f_name(file, fbuf);
			recv_generator(fbuf, file, ndx, itemizing, code, f_out);

			check_for_finished_files(itemizing, code, 0);

			if (i + cur_flist->ndx_start >= next_loopchk) {
				if (allowed_lull)
					maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH);
				else
					maybe_flush_socket(0);
				next_loopchk += loopchk_limit;
			}
		}

		if (!inc_recurse) {
			write_ndx(f_out, NDX_DONE);
			break;
		}

		while (1) {
			check_for_finished_files(itemizing, code, 1);
			if (cur_flist->next || flist_eof)
				break;
			wait_for_receiver();
		}
	} while ((cur_flist = cur_flist->next) != NULL);

	if (delete_during)
		delete_in_dir(NULL, NULL, dev_zero);
	phase++;
	if (DEBUG_GTE(GENR, 1))
		rprintf(FINFO, "generate_files phase=%d\n", phase);

	while (1) {
		check_for_finished_files(itemizing, code, 1);
		if (msgdone_cnt)
			break;
		wait_for_receiver();
	}

	phase++;
	if (DEBUG_GTE(GENR, 1))
		rprintf(FINFO, "generate_files phase=%d\n", phase);

	write_ndx(f_out, NDX_DONE);

	/* Reduce round-trip lag-time for a useless delay-updates phase. */
	if (protocol_version >= 29 && EARLY_DELAY_DONE_MSG())
		write_ndx(f_out, NDX_DONE);

	if (protocol_version >= 31 && EARLY_DELETE_DONE_MSG()) {
		if ((INFO_GTE(STATS, 2) && (delete_mode || force_delete)) || read_batch)
			write_del_stats(f_out);
		if (EARLY_DELAY_DONE_MSG()) /* Can't send this before delay */
			write_ndx(f_out, NDX_DONE);
	}

	/* Read MSG_DONE for the redo phase (and any prior messages). */
	while (1) {
		check_for_finished_files(itemizing, code, 0);
		if (msgdone_cnt > 1)
			break;
		wait_for_receiver();
	}

	if (protocol_version >= 29) {
		phase++;
		if (DEBUG_GTE(GENR, 1))
			rprintf(FINFO, "generate_files phase=%d\n", phase);
		if (!EARLY_DELAY_DONE_MSG()) {
			write_ndx(f_out, NDX_DONE);
			if (protocol_version >= 31 && EARLY_DELETE_DONE_MSG())
				write_ndx(f_out, NDX_DONE);
		}
		/* Read MSG_DONE for delay-updates phase & prior messages. */
		while (msgdone_cnt == 2)
			wait_for_receiver();
	}

	info_levels[INFO_FLIST] = save_info_flist;
	info_levels[INFO_PROGRESS] = save_info_progress;

	if (delete_during == 2)
		do_delayed_deletions(fbuf);
	if (delete_after && !solo_file && file_total > 0)
		do_delete_pass();

	if (max_delete >= 0 && skipped_deletes) {
		rprintf(FWARNING,
			"Deletions stopped due to --max-delete limit (%d skipped)\n",
			skipped_deletes);
		io_error |= IOERR_DEL_LIMIT;
	}

	if (protocol_version >= 31) {
		if (!EARLY_DELETE_DONE_MSG()) {
			if (INFO_GTE(STATS, 2) || read_batch)
				write_del_stats(f_out);
			write_ndx(f_out, NDX_DONE);
		}

		/* Read MSG_DONE for late-delete phase & prior messages. */
		while (msgdone_cnt == 3)
			wait_for_receiver();
	}

	if ((need_retouch_dir_perms || need_retouch_dir_times)
	 && dir_tweaking && (!inc_recurse || delete_during == 2))
		touch_up_dirs(dir_flist, -1);

	if (DEBUG_GTE(GENR, 1))
		rprintf(FINFO, "generate_files finished\n");
}
#include "rsync.h"

 int main(int argc, char *argv[])
{
	STRUCT_STAT st;
	int ret;

	while (--argc > 0) {
#ifdef USE_STAT64_FUNCS
		ret = stat64(*++argv, &st);
#else
		ret = stat(*++argv, &st);
#endif
		if (ret < 0) {
			fprintf(stderr, "Unable to stat `%s'\n", *argv);
			exit(1);
		}
		printf("%ld/%ld\n", (long)major(st.st_dev), (long)minor(st.st_dev));
	}

	return 0;
}
/*
 * Print out the gids of all groups for the current user.  This is like
 * `id -G` on Linux, but it's too hard to find a portable equivalent.
 *
 * Copyright (C) 2002 Martin Pool
 * Copyright (C) 2003-2020 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"

 int main(UNUSED(int argc), UNUSED(char *argv[]))
{
	int n, i;
	gid_t *list;
	gid_t gid = MY_GID();
	int gid_in_list = 0;

#ifdef HAVE_GETGROUPS
	if ((n = getgroups(0, NULL)) < 0) {
		perror("getgroups");
		return 1;
	}
#else
	n = 0;
#endif

	list = (gid_t*)malloc(sizeof (gid_t) * (n + 1));
	if (!list) {
		fprintf(stderr, "out of memory!\n");
		exit(1);
	}

#ifdef HAVE_GETGROUPS
	if (n > 0)
		n = getgroups(n, list);
#endif

	for (i = 0; i < n; i++)  {
		printf("%lu ", (unsigned long)list[i]);
		if (list[i] == gid)
			gid_in_list = 1;
	}
	/* The default gid might not be in the list on some systems. */
	if (!gid_in_list)
		printf("%lu", (unsigned long)gid);
	printf("\n");

	return 0;
}
/*
 * Routines to provide a memory-efficient hashtable.
 *
 * Copyright (C) 2007-2022 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"

#define HASH_LOAD_LIMIT(size) ((size)*3/4)

struct hashtable *hashtable_create(int size, int key64)
{
	int req = size;
	struct hashtable *tbl;
	int node_size = key64 ? sizeof (struct ht_int64_node)
			      : sizeof (struct ht_int32_node);

	/* Pick a power of 2 that can hold the requested size. */
	if (size & (size-1) || size < 16) {
		size = 16;
		while (size < req)
			size *= 2;
	}

	tbl = new(struct hashtable);
	tbl->nodes = new_array0(char, size * node_size);
	tbl->size = size;
	tbl->entries = 0;
	tbl->node_size = node_size;
	tbl->key64 = key64 ? 1 : 0;

	if (DEBUG_GTE(HASH, 1)) {
		char buf[32];
		if (req != size)
			snprintf(buf, sizeof buf, "req: %d, ", req);
		else
			*buf = '\0';
		rprintf(FINFO, "[%s] created hashtable %lx (%ssize: %d, keys: %d-bit)\n",
			who_am_i(), (long)tbl, buf, size, key64 ? 64 : 32);
	}

	return tbl;
}

void hashtable_destroy(struct hashtable *tbl)
{
	if (DEBUG_GTE(HASH, 1)) {
		rprintf(FINFO, "[%s] destroyed hashtable %lx (size: %d, keys: %d-bit)\n",
			who_am_i(), (long)tbl, tbl->size, tbl->key64 ? 64 : 32);
	}
	free(tbl->nodes);
	free(tbl);
}

/* Returns the node that holds the indicated key if it exists. When it does not
 * exist, it returns either NULL (when data_when_new is NULL), or it returns a
 * new node with its node->data set to the indicated value.
 *
 * If your code doesn't know the data value for a new node in advance (usually
 * because it doesn't know if a node is new or not) you should pass in a unique
 * (non-0) value that you can use to check if the returned node is new. You can
 * then overwrite the data with any value you want (even 0) since it only needs
 * to be different than whatever data_when_new value you use later on.
 *
 * This return is a void* just because it might be pointing at a ht_int32_node
 * or a ht_int64_node, and that makes the caller's assignment a little easier. */
void *hashtable_find(struct hashtable *tbl, int64 key, void *data_when_new)
{
	int key64 = tbl->key64;
	struct ht_int32_node *node;
	uint32 ndx;

	if (key64 ? key == 0 : (int32)key == 0) {
		rprintf(FERROR, "Internal hashtable error: illegal key supplied!\n");
		exit_cleanup(RERR_MESSAGEIO);
	}

	if (data_when_new && tbl->entries > HASH_LOAD_LIMIT(tbl->size)) {
		void *old_nodes = tbl->nodes;
		int size = tbl->size * 2;
		int i;

		tbl->nodes = new_array0(char, size * tbl->node_size);
		tbl->size = size;
		tbl->entries = 0;

		if (DEBUG_GTE(HASH, 1)) {
			rprintf(FINFO, "[%s] growing hashtable %lx (size: %d, keys: %d-bit)\n",
				who_am_i(), (long)tbl, size, key64 ? 64 : 32);
		}

		for (i = size / 2; i-- > 0; ) {
			struct ht_int32_node *move_node = HT_NODE(tbl, old_nodes, i);
			int64 move_key = HT_KEY(move_node, key64);
			if (move_key == 0)
				continue;
			if (move_node->data)
				hashtable_find(tbl, move_key, move_node->data);
			else {
				node = hashtable_find(tbl, move_key, "");
				node->data = 0;
			}
		}

		free(old_nodes);
	}

	if (!key64) {
		/* Based on Jenkins One-at-a-time hash. */
		uchar buf[4], *keyp = buf;
		int i;

		SIVALu(buf, 0, key);
		for (ndx = 0, i = 0; i < 4; i++) {
			ndx += keyp[i];
			ndx += (ndx << 10);
			ndx ^= (ndx >> 6);
		}
		ndx += (ndx << 3);
		ndx ^= (ndx >> 11);
		ndx += (ndx << 15);
	} else {
		/* Based on Jenkins hashword() from lookup3.c. */
		uint32 a, b, c;

		/* Set up the internal state */
		a = b = c = 0xdeadbeef + (8 << 2);

#define rot(x,k) (((x)<<(k)) ^ ((x)>>(32-(k))))
#if SIZEOF_INT64 >= 8
		b += (uint32)(key >> 32);
#endif
		a += (uint32)key;
		c ^= b; c -= rot(b, 14);
		a ^= c; a -= rot(c, 11);
		b ^= a; b -= rot(a, 25);
		c ^= b; c -= rot(b, 16);
		a ^= c; a -= rot(c, 4);
		b ^= a; b -= rot(a, 14);
		c ^= b; c -= rot(b, 24);
#undef rot
		ndx = c;
	}

	/* If it already exists, return the node.  If we're not
	 * allocating, return NULL if the key is not found. */
	while (1) {
		int64 nkey;

		ndx &= tbl->size - 1;
		node = HT_NODE(tbl, tbl->nodes, ndx);
		nkey = HT_KEY(node, key64);

		if (nkey == key)
			return node;
		if (nkey == 0) {
			if (!data_when_new)
				return NULL;
			break;
		}
		ndx++;
	}

	/* Take over this empty spot and then return the node. */
	if (key64)
		((struct ht_int64_node*)node)->key = key;
	else
		node->key = (int32)key;
	node->data = data_when_new;
	tbl->entries++;
	return node;
}

#ifndef WORDS_BIGENDIAN
# define HASH_LITTLE_ENDIAN 1
# define HASH_BIG_ENDIAN 0
#else
# define HASH_LITTLE_ENDIAN 0
# define HASH_BIG_ENDIAN 1
#endif

/*
 -------------------------------------------------------------------------------
 lookup3.c, by Bob Jenkins, May 2006, Public Domain.

 These are functions for producing 32-bit hashes for hash table lookup.
 hash_word(), hashlittle(), hashlittle2(), hashbig(), mix(), and final()
 are externally useful functions.  Routines to test the hash are included
 if SELF_TEST is defined.  You can use this free for any purpose.  It's in
 the public domain.  It has no warranty.

 You probably want to use hashlittle().  hashlittle() and hashbig()
 hash byte arrays.  hashlittle() is is faster than hashbig() on
 little-endian machines.  Intel and AMD are little-endian machines.
 On second thought, you probably want hashlittle2(), which is identical to
 hashlittle() except it returns two 32-bit hashes for the price of one.
 You could implement hashbig2() if you wanted but I haven't bothered here.

 If you want to find a hash of, say, exactly 7 integers, do
   a = i1;  b = i2;  c = i3;
   mix(a,b,c);
   a += i4; b += i5; c += i6;
   mix(a,b,c);
   a += i7;
   final(a,b,c);
 then use c as the hash value.  If you have a variable length array of
 4-byte integers to hash, use hash_word().  If you have a byte array (like
 a character string), use hashlittle().  If you have several byte arrays, or
 a mix of things, see the comments above hashlittle().

 Why is this so big?  I read 12 bytes at a time into 3 4-byte integers,
 then mix those integers.  This is fast (you can do a lot more thorough
 mixing with 12*3 instructions on 3 integers than you can with 3 instructions
 on 1 byte), but shoehorning those bytes into integers efficiently is messy.
*/

#define hashsize(n) ((uint32_t)1<<(n))
#define hashmask(n) (hashsize(n)-1)
#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))

/*
 -------------------------------------------------------------------------------
 mix -- mix 3 32-bit values reversibly.

 This is reversible, so any information in (a,b,c) before mix() is
 still in (a,b,c) after mix().

 If four pairs of (a,b,c) inputs are run through mix(), or through
 mix() in reverse, there are at least 32 bits of the output that
 are sometimes the same for one pair and different for another pair.
 This was tested for:
 * pairs that differed by one bit, by two bits, in any combination
   of top bits of (a,b,c), or in any combination of bottom bits of
   (a,b,c).
 * "differ" is defined as +, -, ^, or ~^.  For + and -, I transformed
   the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
   is commonly produced by subtraction) look like a single 1-bit
   difference.
 * the base values were pseudorandom, all zero but one bit set, or
   all zero plus a counter that starts at zero.

 Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that
 satisfy this are
     4  6  8 16 19  4
     9 15  3 18 27 15
    14  9  3  7 17  3
 Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing
 for "differ" defined as + with a one-bit base and a two-bit delta.  I
 used http://burtleburtle.net/bob/hash/avalanche.html to choose
 the operations, constants, and arrangements of the variables.

 This does not achieve avalanche.  There are input bits of (a,b,c)
 that fail to affect some output bits of (a,b,c), especially of a.  The
 most thoroughly mixed value is c, but it doesn't really even achieve
 avalanche in c.

 This allows some parallelism.  Read-after-writes are good at doubling
 the number of bits affected, so the goal of mixing pulls in the opposite
 direction as the goal of parallelism.  I did what I could.  Rotates
 seem to cost as much as shifts on every machine I could lay my hands
 on, and rotates are much kinder to the top and bottom bits, so I used
 rotates.
 -------------------------------------------------------------------------------
*/
#define mix(a,b,c) \
{ \
  a -= c;  a ^= rot(c, 4);  c += b; \
  b -= a;  b ^= rot(a, 6);  a += c; \
  c -= b;  c ^= rot(b, 8);  b += a; \
  a -= c;  a ^= rot(c,16);  c += b; \
  b -= a;  b ^= rot(a,19);  a += c; \
  c -= b;  c ^= rot(b, 4);  b += a; \
}

/*
 -------------------------------------------------------------------------------
 final -- final mixing of 3 32-bit values (a,b,c) into c

 Pairs of (a,b,c) values differing in only a few bits will usually
 produce values of c that look totally different.  This was tested for
 * pairs that differed by one bit, by two bits, in any combination
   of top bits of (a,b,c), or in any combination of bottom bits of
   (a,b,c).
 * "differ" is defined as +, -, ^, or ~^.  For + and -, I transformed
   the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
   is commonly produced by subtraction) look like a single 1-bit
   difference.
 * the base values were pseudorandom, all zero but one bit set, or
   all zero plus a counter that starts at zero.

 These constants passed:
  14 11 25 16 4 14 24
  12 14 25 16 4 14 24
 and these came close:
   4  8 15 26 3 22 24
  10  8 15 26 3 22 24
  11  8 15 26 3 22 24
 -------------------------------------------------------------------------------
*/
#define final(a,b,c) \
{ \
  c ^= b; c -= rot(b,14); \
  a ^= c; a -= rot(c,11); \
  b ^= a; b -= rot(a,25); \
  c ^= b; c -= rot(b,16); \
  a ^= c; a -= rot(c,4);  \
  b ^= a; b -= rot(a,14); \
  c ^= b; c -= rot(b,24); \
}


/*
 -------------------------------------------------------------------------------
 hashlittle() -- hash a variable-length key into a 32-bit value
   k       : the key (the unaligned variable-length array of bytes)
   length  : the length of the key, counting by bytes
   val2    : IN: can be any 4-byte value OUT: second 32 bit hash.
 Returns a 32-bit value.  Every bit of the key affects every bit of
 the return value.  Two keys differing by one or two bits will have
 totally different hash values.  Note that the return value is better
 mixed than val2, so use that first.

 The best hash table sizes are powers of 2.  There is no need to do
 mod a prime (mod is sooo slow!).  If you need less than 32 bits,
 use a bitmask.  For example, if you need only 10 bits, do
   h = (h & hashmask(10));
 In which case, the hash table should have hashsize(10) elements.

 If you are hashing n strings (uint8_t **)k, do it like this:
   for (i=0, h=0; i<n; ++i) h = hashlittle( k[i], len[i], h);

 By Bob Jenkins, 2006.  bob_jenkins@burtleburtle.net.  You may use this
 code any way you wish, private, educational, or commercial.  It's free.

 Use for hash table lookup, or anything where one collision in 2^^32 is
 acceptable.  Do NOT use for cryptographic purposes.
 -------------------------------------------------------------------------------
*/

#define NON_ZERO_32(x) ((x) ? (x) : (uint32_t)1)
#define NON_ZERO_64(x, y) ((x) || (y) ? (y) | (int64)(x) << 32 | (y) : (int64)1)

uint32_t hashlittle(const void *key, size_t length)
{
  uint32_t a,b,c;                                          /* internal state */
  union { const void *ptr; size_t i; } u;     /* needed for Mac Powerbook G4 */

  /* Set up the internal state */
  a = b = c = 0xdeadbeef + ((uint32_t)length);

  u.ptr = key;
  if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) {
    const uint32_t *k = (const uint32_t *)key;         /* read 32-bit chunks */
    const uint8_t  *k8;

    /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
    while (length > 12)
    {
      a += k[0];
      b += k[1];
      c += k[2];
      mix(a,b,c);
      length -= 12;
      k += 3;
    }

    /*----------------------------- handle the last (probably partial) block */
    k8 = (const uint8_t *)k;
    switch(length)
    {
    case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
    case 11: c+=((uint32_t)k8[10])<<16;  /* fall through */
    case 10: c+=((uint32_t)k8[9])<<8;    /* fall through */
    case 9 : c+=k8[8];                   /* fall through */
    case 8 : b+=k[1]; a+=k[0]; break;
    case 7 : b+=((uint32_t)k8[6])<<16;   /* fall through */
    case 6 : b+=((uint32_t)k8[5])<<8;    /* fall through */
    case 5 : b+=k8[4];                   /* fall through */
    case 4 : a+=k[0]; break;
    case 3 : a+=((uint32_t)k8[2])<<16;   /* fall through */
    case 2 : a+=((uint32_t)k8[1])<<8;    /* fall through */
    case 1 : a+=k8[0]; break;
    case 0 : return NON_ZERO_32(c);
    }
  } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) {
    const uint16_t *k = (const uint16_t *)key;         /* read 16-bit chunks */
    const uint8_t  *k8;

    /*--------------- all but last block: aligned reads and different mixing */
    while (length > 12)
    {
      a += k[0] + (((uint32_t)k[1])<<16);
      b += k[2] + (((uint32_t)k[3])<<16);
      c += k[4] + (((uint32_t)k[5])<<16);
      mix(a,b,c);
      length -= 12;
      k += 6;
    }

    /*----------------------------- handle the last (probably partial) block */
    k8 = (const uint8_t *)k;
    switch(length)
    {
    case 12: c+=k[4]+(((uint32_t)k[5])<<16);
             b+=k[2]+(((uint32_t)k[3])<<16);
             a+=k[0]+(((uint32_t)k[1])<<16);
             break;
    case 11: c+=((uint32_t)k8[10])<<16;     /* fall through */
    case 10: c+=k[4];
             b+=k[2]+(((uint32_t)k[3])<<16);
             a+=k[0]+(((uint32_t)k[1])<<16);
             break;
    case 9 : c+=k8[8];                      /* fall through */
    case 8 : b+=k[2]+(((uint32_t)k[3])<<16);
             a+=k[0]+(((uint32_t)k[1])<<16);
             break;
    case 7 : b+=((uint32_t)k8[6])<<16;      /* fall through */
    case 6 : b+=k[2];
             a+=k[0]+(((uint32_t)k[1])<<16);
             break;
    case 5 : b+=k8[4];                      /* fall through */
    case 4 : a+=k[0]+(((uint32_t)k[1])<<16);
             break;
    case 3 : a+=((uint32_t)k8[2])<<16;      /* fall through */
    case 2 : a+=k[0];
             break;
    case 1 : a+=k8[0];
             break;
    case 0 : return NON_ZERO_32(c);         /* zero length requires no mixing */
    }

  } else {                        /* need to read the key one byte at a time */
    const uint8_t *k = (const uint8_t *)key;

    /*--------------- all but the last block: affect some 32 bits of (a,b,c) */
    while (length > 12)
    {
      a += k[0];
      a += ((uint32_t)k[1])<<8;
      a += ((uint32_t)k[2])<<16;
      a += ((uint32_t)k[3])<<24;
      b += k[4];
      b += ((uint32_t)k[5])<<8;
      b += ((uint32_t)k[6])<<16;
      b += ((uint32_t)k[7])<<24;
      c += k[8];
      c += ((uint32_t)k[9])<<8;
      c += ((uint32_t)k[10])<<16;
      c += ((uint32_t)k[11])<<24;
      mix(a,b,c);
      length -= 12;
      k += 12;
    }

    /*-------------------------------- last block: affect all 32 bits of (c) */
    switch(length)                   /* all the case statements fall through */
    {
    case 12: c+=((uint32_t)k[11])<<24;
	     /* FALLTHROUGH */
    case 11: c+=((uint32_t)k[10])<<16;
	     /* FALLTHROUGH */
    case 10: c+=((uint32_t)k[9])<<8;
	     /* FALLTHROUGH */
    case 9 : c+=k[8];
	     /* FALLTHROUGH */
    case 8 : b+=((uint32_t)k[7])<<24;
	     /* FALLTHROUGH */
    case 7 : b+=((uint32_t)k[6])<<16;
	     /* FALLTHROUGH */
    case 6 : b+=((uint32_t)k[5])<<8;
	     /* FALLTHROUGH */
    case 5 : b+=k[4];
	     /* FALLTHROUGH */
    case 4 : a+=((uint32_t)k[3])<<24;
	     /* FALLTHROUGH */
    case 3 : a+=((uint32_t)k[2])<<16;
	     /* FALLTHROUGH */
    case 2 : a+=((uint32_t)k[1])<<8;
	     /* FALLTHROUGH */
    case 1 : a+=k[0];
             break;
    case 0 : return NON_ZERO_32(c);
    }
  }

  final(a,b,c);
  return NON_ZERO_32(c);
}

#if SIZEOF_INT64 >= 8
/*
 * hashlittle2: return 2 32-bit hash values joined into an int64.
 *
 * This is identical to hashlittle(), except it returns two 32-bit hash
 * values instead of just one.  This is good enough for hash table
 * lookup with 2^^64 buckets, or if you want a second hash if you're not
 * happy with the first, or if you want a probably-unique 64-bit ID for
 * the key.  *pc is better mixed than *pb, so use *pc first.  If you want
 * a 64-bit value do something like "*pc + (((uint64_t)*pb)<<32)".
 */
int64 hashlittle2(const void *key, size_t length)
{
  uint32_t a,b,c;                                          /* internal state */
  union { const void *ptr; size_t i; } u;     /* needed for Mac Powerbook G4 */

  /* Set up the internal state */
  a = b = c = 0xdeadbeef + ((uint32_t)length);

  u.ptr = key;
  if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) {
    const uint32_t *k = (const uint32_t *)key;         /* read 32-bit chunks */
    const uint8_t  *k8;

    /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
    while (length > 12)
    {
      a += k[0];
      b += k[1];
      c += k[2];
      mix(a,b,c);
      length -= 12;
      k += 3;
    }

    /*----------------------------- handle the last (probably partial) block */
    k8 = (const uint8_t *)k;
    switch(length)
    {
    case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
    case 11: c+=((uint32_t)k8[10])<<16;  /* fall through */
    case 10: c+=((uint32_t)k8[9])<<8;    /* fall through */
    case 9 : c+=k8[8];                   /* fall through */
    case 8 : b+=k[1]; a+=k[0]; break;
    case 7 : b+=((uint32_t)k8[6])<<16;   /* fall through */
    case 6 : b+=((uint32_t)k8[5])<<8;    /* fall through */
    case 5 : b+=k8[4];                   /* fall through */
    case 4 : a+=k[0]; break;
    case 3 : a+=((uint32_t)k8[2])<<16;   /* fall through */
    case 2 : a+=((uint32_t)k8[1])<<8;    /* fall through */
    case 1 : a+=k8[0]; break;
    case 0 : return NON_ZERO_64(b, c);
    }
  } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) {
    const uint16_t *k = (const uint16_t *)key;         /* read 16-bit chunks */
    const uint8_t  *k8;

    /*--------------- all but last block: aligned reads and different mixing */
    while (length > 12)
    {
      a += k[0] + (((uint32_t)k[1])<<16);
      b += k[2] + (((uint32_t)k[3])<<16);
      c += k[4] + (((uint32_t)k[5])<<16);
      mix(a,b,c);
      length -= 12;
      k += 6;
    }

    /*----------------------------- handle the last (probably partial) block */
    k8 = (const uint8_t *)k;
    switch(length)
    {
    case 12: c+=k[4]+(((uint32_t)k[5])<<16);
             b+=k[2]+(((uint32_t)k[3])<<16);
             a+=k[0]+(((uint32_t)k[1])<<16);
             break;
    case 11: c+=((uint32_t)k8[10])<<16;     /* fall through */
    case 10: c+=k[4];
             b+=k[2]+(((uint32_t)k[3])<<16);
             a+=k[0]+(((uint32_t)k[1])<<16);
             break;
    case 9 : c+=k8[8];                      /* fall through */
    case 8 : b+=k[2]+(((uint32_t)k[3])<<16);
             a+=k[0]+(((uint32_t)k[1])<<16);
             break;
    case 7 : b+=((uint32_t)k8[6])<<16;      /* fall through */
    case 6 : b+=k[2];
             a+=k[0]+(((uint32_t)k[1])<<16);
             break;
    case 5 : b+=k8[4];                      /* fall through */
    case 4 : a+=k[0]+(((uint32_t)k[1])<<16);
             break;
    case 3 : a+=((uint32_t)k8[2])<<16;      /* fall through */
    case 2 : a+=k[0];
             break;
    case 1 : a+=k8[0];
             break;
    case 0 : return NON_ZERO_64(b, c);  /* zero length strings require no mixing */
    }

  } else {                        /* need to read the key one byte at a time */
    const uint8_t *k = (const uint8_t *)key;

    /*--------------- all but the last block: affect some 32 bits of (a,b,c) */
    while (length > 12)
    {
      a += k[0];
      a += ((uint32_t)k[1])<<8;
      a += ((uint32_t)k[2])<<16;
      a += ((uint32_t)k[3])<<24;
      b += k[4];
      b += ((uint32_t)k[5])<<8;
      b += ((uint32_t)k[6])<<16;
      b += ((uint32_t)k[7])<<24;
      c += k[8];
      c += ((uint32_t)k[9])<<8;
      c += ((uint32_t)k[10])<<16;
      c += ((uint32_t)k[11])<<24;
      mix(a,b,c);
      length -= 12;
      k += 12;
    }

    /*-------------------------------- last block: affect all 32 bits of (c) */
    switch(length)                   /* all the case statements fall through */
    {
    case 12: c+=((uint32_t)k[11])<<24;
	     /* FALLTHROUGH */
    case 11: c+=((uint32_t)k[10])<<16;
	     /* FALLTHROUGH */
    case 10: c+=((uint32_t)k[9])<<8;
	     /* FALLTHROUGH */
    case 9 : c+=k[8];
	     /* FALLTHROUGH */
    case 8 : b+=((uint32_t)k[7])<<24;
	     /* FALLTHROUGH */
    case 7 : b+=((uint32_t)k[6])<<16;
	     /* FALLTHROUGH */
    case 6 : b+=((uint32_t)k[5])<<8;
	     /* FALLTHROUGH */
    case 5 : b+=k[4];
	     /* FALLTHROUGH */
    case 4 : a+=((uint32_t)k[3])<<24;
	     /* FALLTHROUGH */
    case 3 : a+=((uint32_t)k[2])<<16;
	     /* FALLTHROUGH */
    case 2 : a+=((uint32_t)k[1])<<8;
	     /* FALLTHROUGH */
    case 1 : a+=k[0];
             break;
    case 0 : return NON_ZERO_64(b, c);
    }
  }

  final(a,b,c);
  return NON_ZERO_64(b, c);
}
#else
#define hashlittle2(key, len) hashlittle(key, len)
#endif
/*
 * Routines to support hard-linking.
 *
 * Copyright (C) 1996 Andrew Tridgell
 * Copyright (C) 1996 Paul Mackerras
 * Copyright (C) 2002 Martin Pool <mbp@samba.org>
 * Copyright (C) 2004-2022 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"
#include "inums.h"
#include "ifuncs.h"

extern int dry_run;
extern int list_only;
extern int am_sender;
extern int inc_recurse;
extern int do_xfers;
extern int alt_dest_type;
extern int preserve_acls;
extern int preserve_xattrs;
extern int protocol_version;
extern int remove_source_files;
extern int stdout_format_has_i;
extern int maybe_ATTRS_REPORT;
extern int unsort_ndx;
extern char *basis_dir[MAX_BASIS_DIRS+1];
extern struct file_list *cur_flist;

#ifdef SUPPORT_HARD_LINKS

/* Starting with protocol 30, we use a simple hashtable on the sending side
 * for hashing the st_dev and st_ino info.  The receiving side gets told
 * (via flags and a "group index") which items are hard-linked together, so
 * we can avoid the pool of dev+inode data.  For incremental recursion mode,
 * the receiver will use a ndx hash to remember old pathnames. */

static void *data_when_new = "";

static struct hashtable *dev_tbl;

static struct hashtable *prior_hlinks;

static struct file_list *hlink_flist;

void init_hard_links(void)
{
	if (am_sender || protocol_version < 30)
		dev_tbl = hashtable_create(16, HT_KEY64);
	else if (inc_recurse)
		prior_hlinks = hashtable_create(1024, HT_KEY32);
}

struct ht_int64_node *idev_find(int64 dev, int64 ino)
{
	static struct ht_int64_node *dev_node = NULL;

	/* Note that some OSes have a dev == 0, so increment to avoid storing a 0. */
	if (!dev_node || dev_node->key != dev+1) {
		/* We keep a separate hash table of inodes for every device. */
		dev_node = hashtable_find(dev_tbl, dev+1, data_when_new);
		if (dev_node->data == data_when_new) {
			dev_node->data = hashtable_create(512, HT_KEY64);
			if (DEBUG_GTE(HLINK, 3)) {
				rprintf(FINFO, "[%s] created hashtable for dev %s\n",
					who_am_i(), big_num(dev));
			}
		}
	}

	return hashtable_find(dev_node->data, ino, (void*)-1L);
}

void idev_destroy(void)
{
	int i;

	for (i = 0; i < dev_tbl->size; i++) {
		struct ht_int32_node *node = HT_NODE(dev_tbl, dev_tbl->nodes, i);
		if (node->data)
			hashtable_destroy(node->data);
	}

	hashtable_destroy(dev_tbl);
}

static int hlink_compare_gnum(int *int1, int *int2)
{
	struct file_struct *f1 = hlink_flist->sorted[*int1];
	struct file_struct *f2 = hlink_flist->sorted[*int2];
	int32 gnum1 = F_HL_GNUM(f1);
	int32 gnum2 = F_HL_GNUM(f2);

	if (gnum1 != gnum2)
		return gnum1 > gnum2 ? 1 : -1;

	return *int1 > *int2 ? 1 : -1;
}

static void match_gnums(int32 *ndx_list, int ndx_count)
{
	int32 from, prev;
	struct file_struct *file, *file_next;
	struct ht_int32_node *node = NULL;
	int32 gnum, gnum_next;

	qsort(ndx_list, ndx_count, sizeof ndx_list[0], (int (*)(const void*, const void*))hlink_compare_gnum);

	for (from = 0; from < ndx_count; from++) {
		file = hlink_flist->sorted[ndx_list[from]];
		gnum = F_HL_GNUM(file);
		if (inc_recurse) {
			node = hashtable_find(prior_hlinks, gnum, data_when_new);
			if (node->data == data_when_new) {
				node->data = new_array0(char, 5);
				assert(gnum >= hlink_flist->ndx_start);
				file->flags |= FLAG_HLINK_FIRST;
				prev = -1;
			} else if (CVAL(node->data, 0) == 0) {
				struct file_list *flist;
				prev = IVAL(node->data, 1);
				flist = flist_for_ndx(prev, NULL);
				if (flist)
					flist->files[prev - flist->ndx_start]->flags &= ~FLAG_HLINK_LAST;
				else {
					/* We skipped all prior files in this
					 * group, so mark this as a "first". */
					file->flags |= FLAG_HLINK_FIRST;
					prev = -1;
				}
			} else
				prev = -1;
		} else {
			file->flags |= FLAG_HLINK_FIRST;
			prev = -1;
		}
		for ( ; from < ndx_count-1; file = file_next, gnum = gnum_next, from++) { /*SHARED ITERATOR*/
			file_next = hlink_flist->sorted[ndx_list[from+1]];
			gnum_next = F_HL_GNUM(file_next);
			if (gnum != gnum_next)
				break;
			F_HL_PREV(file) = prev;
			/* The linked list uses over-the-wire ndx values. */
			if (unsort_ndx)
				prev = F_NDX(file);
			else
				prev = ndx_list[from] + hlink_flist->ndx_start;
		}
		if (prev < 0 && !inc_recurse) {
			/* Disable hard-link bit and set DONE so that
			 * HLINK_BUMP()-dependent values are unaffected. */
			file->flags &= ~(FLAG_HLINKED | FLAG_HLINK_FIRST);
			file->flags |= FLAG_HLINK_DONE;
			continue;
		}

		file->flags |= FLAG_HLINK_LAST;
		F_HL_PREV(file) = prev;
		if (inc_recurse && CVAL(node->data, 0) == 0) {
			if (unsort_ndx)
				prev = F_NDX(file);
			else
				prev = ndx_list[from] + hlink_flist->ndx_start;
			SIVAL(node->data, 1, prev);
		}
	}
}

/* Analyze the hard-links in the file-list by creating a list of all the
 * items that have hlink data, sorting them, and matching up identical
 * values into clusters.  These will be a single linked list from last
 * to first when we're done. */
void match_hard_links(struct file_list *flist)
{
	if (!list_only && flist->used) {
		int i, ndx_count = 0;
		int32 *ndx_list;

		ndx_list = new_array(int32, flist->used);

		for (i = 0; i < flist->used; i++) {
			if (F_IS_HLINKED(flist->sorted[i]))
				ndx_list[ndx_count++] = i;
		}

		hlink_flist = flist;

		if (ndx_count)
			match_gnums(ndx_list, ndx_count);

		free(ndx_list);
	}
	if (protocol_version < 30)
		idev_destroy();
}

static int maybe_hard_link(struct file_struct *file, int ndx,
			   char *fname, int statret, stat_x *sxp,
			   const char *oldname, STRUCT_STAT *old_stp,
			   const char *realname, int itemizing, enum logcode code)
{
	if (statret == 0) {
		if (sxp->st.st_dev == old_stp->st_dev
		 && sxp->st.st_ino == old_stp->st_ino) {
			if (itemizing) {
				itemize(fname, file, ndx, statret, sxp,
					ITEM_LOCAL_CHANGE | ITEM_XNAME_FOLLOWS,
					0, "");
			}
			if (INFO_GTE(NAME, 2) && maybe_ATTRS_REPORT)
				rprintf(FCLIENT, "%s is uptodate\n", fname);
			file->flags |= FLAG_HLINK_DONE;
			return 0;
		}
	}

	if (atomic_create(file, fname, NULL, oldname, MAKEDEV(0, 0), sxp, statret == 0 ? DEL_FOR_FILE : 0)) {
		if (itemizing) {
			itemize(fname, file, ndx, statret, sxp,
				ITEM_LOCAL_CHANGE | ITEM_XNAME_FOLLOWS, 0,
				realname);
		}
		if (code != FNONE && INFO_GTE(NAME, 1))
			rprintf(code, "%s => %s\n", fname, realname);
		return 0;
	}

	return -1;
}

/* Figure out if a prior entry is still there or if we just have a
 * cached name for it. */
static char *check_prior(struct file_struct *file, int gnum,
			 int *prev_ndx_p, struct file_list **flist_p)
{
	struct file_struct *fp;
	struct ht_int32_node *node;
	int prev_ndx = F_HL_PREV(file);

	while (1) {
		struct file_list *flist;
		if (prev_ndx < 0
		 || (flist = flist_for_ndx(prev_ndx, NULL)) == NULL)
			break;
		fp = flist->files[prev_ndx - flist->ndx_start];
		if (!(fp->flags & FLAG_SKIP_HLINK)) {
			*prev_ndx_p = prev_ndx;
			*flist_p = flist;
			return NULL;
		}
		F_HL_PREV(file) = prev_ndx = F_HL_PREV(fp);
	}

	if (inc_recurse
	 && (node = hashtable_find(prior_hlinks, gnum, NULL)) != NULL) {
		assert(node->data != NULL);
		if (CVAL(node->data, 0) != 0) {
			*prev_ndx_p = -1;
			*flist_p = NULL;
			return node->data;
		}
		/* The prior file must have been skipped. */
		F_HL_PREV(file) = -1;
	}

	*prev_ndx_p = -1;
	*flist_p = NULL;
	return NULL;
}

/* Only called if FLAG_HLINKED is set and FLAG_HLINK_FIRST is not.  Returns:
 * 0 = process the file, 1 = skip the file, -1 = error occurred. */
int hard_link_check(struct file_struct *file, int ndx, char *fname,
		    int statret, stat_x *sxp, int itemizing,
		    enum logcode code)
{
	STRUCT_STAT prev_st;
	char namebuf[MAXPATHLEN], altbuf[MAXPATHLEN];
	char *realname, *prev_name;
	struct file_list *flist;
	int gnum = inc_recurse ? F_HL_GNUM(file) : -1;
	int prev_ndx;

	prev_name = realname = check_prior(file, gnum, &prev_ndx, &flist);

	if (!prev_name) {
		struct file_struct *prev_file;

		if (!flist) {
			/* The previous file was skipped, so this one is
			 * treated as if it were the first in its group. */
			if (DEBUG_GTE(HLINK, 2)) {
				rprintf(FINFO, "hlink for %d (%s,%d): virtual first\n",
					ndx, f_name(file, NULL), gnum);
			}
			return 0;
		}

		prev_file = flist->files[prev_ndx - flist->ndx_start];

		/* Is the previous link not complete yet? */
		if (!(prev_file->flags & FLAG_HLINK_DONE)) {
			/* Is the previous link being transferred? */
			if (prev_file->flags & FLAG_FILE_SENT) {
				/* Add ourselves to the list of files that will
				 * be updated when the transfer completes, and
				 * mark ourself as waiting for the transfer. */
				F_HL_PREV(file) = F_HL_PREV(prev_file);
				F_HL_PREV(prev_file) = ndx;
				file->flags |= FLAG_FILE_SENT;
				cur_flist->in_progress++;
				if (DEBUG_GTE(HLINK, 2)) {
					rprintf(FINFO, "hlink for %d (%s,%d): waiting for %d\n",
						ndx, f_name(file, NULL), gnum, F_HL_PREV(file));
				}
				return 1;
			}
			if (DEBUG_GTE(HLINK, 2)) {
				rprintf(FINFO, "hlink for %d (%s,%d): looking for a leader\n",
					ndx, f_name(file, NULL), gnum);
			}
			return 0;
		}

		/* There is a finished file to link with! */
		if (!(prev_file->flags & FLAG_HLINK_FIRST)) {
			/* The previous previous is FIRST when prev is not. */
			prev_name = realname = check_prior(prev_file, gnum, &prev_ndx, &flist);
			/* Update our previous pointer to point to the FIRST. */
			F_HL_PREV(file) = prev_ndx;
		}

		if (!prev_name) {
			int alt_dest;

			assert(flist != NULL);
			prev_file = flist->files[prev_ndx - flist->ndx_start];
			/* F_HL_PREV() is alt_dest value when DONE && FIRST. */
			alt_dest = F_HL_PREV(prev_file);
			if (DEBUG_GTE(HLINK, 2)) {
				rprintf(FINFO, "hlink for %d (%s,%d): found flist match (alt %d)\n",
					ndx, f_name(file, NULL), gnum, alt_dest);
			}

			if (alt_dest >= 0 && dry_run) {
				pathjoin(namebuf, MAXPATHLEN, basis_dir[alt_dest],
					 f_name(prev_file, NULL));
				prev_name = namebuf;
				realname = f_name(prev_file, altbuf);
			} else {
				prev_name = f_name(prev_file, namebuf);
				realname = prev_name;
			}
		}
	}

	if (DEBUG_GTE(HLINK, 2)) {
		rprintf(FINFO, "hlink for %d (%s,%d): leader is %d (%s)\n",
			ndx, f_name(file, NULL), gnum, prev_ndx, prev_name);
	}

	if (link_stat(prev_name, &prev_st, 0) < 0) {
		if (!dry_run || errno != ENOENT) {
			rsyserr(FERROR_XFER, errno, "stat %s failed", full_fname(prev_name));
			return -1;
		}
		/* A new hard-link will get a new dev & inode, so approximate
		 * those values in dry-run mode by zeroing them. */
		memset(&prev_st, 0, sizeof prev_st);
	}

	if (statret < 0 && basis_dir[0] != NULL) {
		/* If we match an alt-dest item, we don't output this as a change. */
		char cmpbuf[MAXPATHLEN];
		stat_x alt_sx;
		int j = 0;
		init_stat_x(&alt_sx);
		do {
			pathjoin(cmpbuf, MAXPATHLEN, basis_dir[j], fname);
			if (link_stat(cmpbuf, &alt_sx.st, 0) < 0)
				continue;
			if (alt_dest_type == LINK_DEST) {
				if (prev_st.st_dev != alt_sx.st.st_dev
				 || prev_st.st_ino != alt_sx.st.st_ino)
					continue;
				statret = 1;
				if (stdout_format_has_i == 0
				 || (!INFO_GTE(NAME, 2) && stdout_format_has_i < 2)) {
					itemizing = 0;
					code = FNONE;
					if (INFO_GTE(NAME, 2) && maybe_ATTRS_REPORT)
						rprintf(FCLIENT, "%s is uptodate\n", fname);
				}
				break;
			}
			if (!quick_check_ok(FT_REG, cmpbuf, file, &alt_sx.st))
				continue;
			statret = 1;
			if (unchanged_attrs(cmpbuf, file, &alt_sx))
				break;
		} while (basis_dir[++j] != NULL);
		if (statret == 1) {
			sxp->st = alt_sx.st;
#ifdef SUPPORT_ACLS
			if (preserve_acls && !S_ISLNK(file->mode)) {
				free_acl(sxp);
				if (!ACL_READY(alt_sx))
					get_acl(cmpbuf, sxp);
				else {
					sxp->acc_acl = alt_sx.acc_acl;
					sxp->def_acl = alt_sx.def_acl;
					alt_sx.acc_acl = alt_sx.def_acl = NULL;
				}
			}
#endif
#ifdef SUPPORT_XATTRS
			if (preserve_xattrs) {
				free_xattr(sxp);
				if (!XATTR_READY(alt_sx))
					get_xattr(cmpbuf, sxp);
				else {
					sxp->xattr = alt_sx.xattr;
					alt_sx.xattr = NULL;
				}
			}
#endif
		} else
			free_stat_x(&alt_sx);
	}

	if (maybe_hard_link(file, ndx, fname, statret, sxp, prev_name, &prev_st,
			    realname, itemizing, code) < 0)
		return -1;

	if (remove_source_files == 1 && do_xfers)
		send_msg_success(fname, ndx);

	return 1;
}

int hard_link_one(struct file_struct *file, const char *fname,
		  const char *oldname, int terse)
{
	if (do_link(oldname, fname) < 0) {
		enum logcode code;
		if (terse) {
			if (!INFO_GTE(NAME, 1))
				return 0;
			code = FINFO;
		} else
			code = FERROR_XFER;
		rsyserr(code, errno, "link %s => %s failed",
			full_fname(fname), oldname);
		return 0;
	}

	file->flags |= FLAG_HLINK_DONE;

	return 1;
}

void finish_hard_link(struct file_struct *file, const char *fname, int fin_ndx,
		      STRUCT_STAT *stp, int itemizing, enum logcode code,
		      int alt_dest)
{
	stat_x prev_sx;
	STRUCT_STAT st;
	char prev_name[MAXPATHLEN], alt_name[MAXPATHLEN];
	const char *our_name;
	struct file_list *flist;
	int prev_statret, ndx, prev_ndx = F_HL_PREV(file);

	if (stp == NULL && prev_ndx >= 0) {
		if (link_stat(fname, &st, 0) < 0 && !dry_run) {
			rsyserr(FERROR_XFER, errno, "stat %s failed",
				full_fname(fname));
			return;
		}
		stp = &st;
	}

	/* FIRST combined with DONE means we were the first to get done. */
	file->flags |= FLAG_HLINK_FIRST | FLAG_HLINK_DONE;
	F_HL_PREV(file) = alt_dest;
	if (alt_dest >= 0 && dry_run) {
		pathjoin(alt_name, MAXPATHLEN, basis_dir[alt_dest],
			 f_name(file, NULL));
		our_name = alt_name;
	} else
		our_name = fname;

	init_stat_x(&prev_sx);

	while ((ndx = prev_ndx) >= 0) {
		int val;
		flist = flist_for_ndx(ndx, "finish_hard_link");
		file = flist->files[ndx - flist->ndx_start];
		file->flags = (file->flags & ~FLAG_HLINK_FIRST) | FLAG_HLINK_DONE;
		prev_ndx = F_HL_PREV(file);
		F_HL_PREV(file) = fin_ndx;
		prev_statret = link_stat(f_name(file, prev_name), &prev_sx.st, 0);
		val = maybe_hard_link(file, ndx, prev_name, prev_statret, &prev_sx,
				      our_name, stp, fname, itemizing, code);
		flist->in_progress--;
		free_stat_x(&prev_sx);
		if (val < 0)
			continue;
		if (remove_source_files == 1 && do_xfers)
			send_msg_success(fname, ndx);
	}

	if (inc_recurse) {
		int gnum = F_HL_GNUM(file);
		struct ht_int32_node *node = hashtable_find(prior_hlinks, gnum, NULL);
		if (node == NULL) {
			rprintf(FERROR, "Unable to find a hlink node for %d (%s)\n", gnum, f_name(file, prev_name));
			exit_cleanup(RERR_MESSAGEIO);
		}
		if (node->data == NULL) {
			rprintf(FERROR, "Hlink node data for %d is NULL (%s)\n", gnum, f_name(file, prev_name));
			exit_cleanup(RERR_MESSAGEIO);
		}
		if (CVAL(node->data, 0) != 0) {
			rprintf(FERROR, "Hlink node data for %d already has path=%s (%s)\n",
				gnum, (char*)node->data, f_name(file, prev_name));
			exit_cleanup(RERR_MESSAGEIO);
		}
		free(node->data);
		node->data = strdup(our_name);
	}
}

int skip_hard_link(struct file_struct *file, struct file_list **flist_p)
{
	struct file_list *flist;
	int prev_ndx;

	file->flags |= FLAG_SKIP_HLINK;
	if (!(file->flags & FLAG_HLINK_LAST))
		return -1;

	check_prior(file, F_HL_GNUM(file), &prev_ndx, &flist);
	if (prev_ndx >= 0) {
		file = flist->files[prev_ndx - flist->ndx_start];
		if (file->flags & (FLAG_HLINK_DONE|FLAG_FILE_SENT))
			return -1;
		file->flags |= FLAG_HLINK_LAST;
		*flist_p = flist;
	}

	return prev_ndx;
}
#endif
/*
 * Socket and pipe I/O utilities used in rsync.
 *
 * Copyright (C) 1996-2001 Andrew Tridgell
 * Copyright (C) 1996 Paul Mackerras
 * Copyright (C) 2001, 2002 Martin Pool <mbp@samba.org>
 * Copyright (C) 2003-2022 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

/* Rsync provides its own multiplexing system, which is used to send
 * stderr and stdout over a single socket.
 *
 * For historical reasons this is off during the start of the
 * connection, but it's switched on quite early using
 * io_start_multiplex_out() and io_start_multiplex_in(). */

#include "rsync.h"
#include "ifuncs.h"
#include "inums.h"

/** If no timeout is specified then use a 60 second select timeout */
#define SELECT_TIMEOUT 60

extern int bwlimit;
extern size_t bwlimit_writemax;
extern int io_timeout;
extern int am_server;
extern int am_sender;
extern int am_receiver;
extern int am_generator;
extern int local_server;
extern int msgs2stderr;
extern int inc_recurse;
extern int io_error;
extern int batch_fd;
extern int eol_nulls;
extern int flist_eof;
extern int file_total;
extern int file_old_total;
extern int list_only;
extern int read_batch;
extern int compat_flags;
extern int protect_args;
extern int checksum_seed;
extern int xfer_sum_len;
extern int daemon_connection;
extern int protocol_version;
extern int remove_source_files;
extern int preserve_hard_links;
extern BOOL extra_flist_sending_enabled;
extern BOOL flush_ok_after_signal;
extern struct stats stats;
extern time_t stop_at_utime;
extern struct file_list *cur_flist;
#ifdef ICONV_OPTION
extern int filesfrom_convert;
extern iconv_t ic_send, ic_recv;
#endif

int csum_length = SHORT_SUM_LENGTH; /* initial value */
int allowed_lull = 0;
int msgdone_cnt = 0;
int forward_flist_data = 0;
BOOL flist_receiving_enabled = False;

/* Ignore an EOF error if non-zero. See whine_about_eof(). */
int kluge_around_eof = 0;
int got_kill_signal = -1; /* is set to 0 only after multiplexed I/O starts */

int sock_f_in = -1;
int sock_f_out = -1;

int64 total_data_read = 0;
int64 total_data_written = 0;

char num_dev_ino_buf[4 + 8 + 8];

static struct {
	xbuf in, out, msg;
	int in_fd;
	int out_fd; /* Both "out" and "msg" go to this fd. */
	int in_multiplexed;
	unsigned out_empty_len;
	size_t raw_data_header_pos;      /* in the out xbuf */
	size_t raw_flushing_ends_before; /* in the out xbuf */
	size_t raw_input_ends_before;    /* in the in xbuf */
} iobuf = { .in_fd = -1, .out_fd = -1 };

static time_t last_io_in;
static time_t last_io_out;

static int write_batch_monitor_in = -1;
static int write_batch_monitor_out = -1;

static int ff_forward_fd = -1;
static int ff_reenable_multiplex = -1;
static char ff_lastchar = '\0';
static xbuf ff_xb = EMPTY_XBUF;
#ifdef ICONV_OPTION
static xbuf iconv_buf = EMPTY_XBUF;
#endif
static int select_timeout = SELECT_TIMEOUT;
static int active_filecnt = 0;
static OFF_T active_bytecnt = 0;
static int first_message = 1;

static char int_byte_extra[64] = {
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* (00 - 3F)/4 */
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* (40 - 7F)/4 */
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* (80 - BF)/4 */
	2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 5, 6, /* (C0 - FF)/4 */
};

/* Our I/O buffers are sized with no bits on in the lowest byte of the "size"
 * (indeed, our rounding of sizes in 1024-byte units assures more than this).
 * This allows the code that is storing bytes near the physical end of a
 * circular buffer to temporarily reduce the buffer's size (in order to make
 * some storing idioms easier), while also making it simple to restore the
 * buffer's actual size when the buffer's "pos" wraps around to the start (we
 * just round the buffer's size up again). */

#define IOBUF_WAS_REDUCED(siz) ((siz) & 0xFF)
#define IOBUF_RESTORE_SIZE(siz) (((siz) | 0xFF) + 1)

#define IN_MULTIPLEXED (iobuf.in_multiplexed != 0)
#define IN_MULTIPLEXED_AND_READY (iobuf.in_multiplexed > 0)
#define OUT_MULTIPLEXED (iobuf.out_empty_len != 0)

#define PIO_NEED_INPUT (1<<0) /* The *_NEED_* flags are mutually exclusive. */
#define PIO_NEED_OUTROOM (1<<1)
#define PIO_NEED_MSGROOM (1<<2)

#define PIO_CONSUME_INPUT (1<<4) /* Must becombined with PIO_NEED_INPUT. */

#define PIO_INPUT_AND_CONSUME (PIO_NEED_INPUT | PIO_CONSUME_INPUT)
#define PIO_NEED_FLAGS (PIO_NEED_INPUT | PIO_NEED_OUTROOM | PIO_NEED_MSGROOM)

#define REMOTE_OPTION_ERROR "rsync: on remote machine: -"
#define REMOTE_OPTION_ERROR2 ": unknown option"

#define FILESFROM_BUFLEN 2048

enum festatus { FES_SUCCESS, FES_REDO, FES_NO_SEND };

static flist_ndx_list redo_list, hlink_list;

static void read_a_msg(void);
static void drain_multiplex_messages(void);
static void sleep_for_bwlimit(int bytes_written);

static void check_timeout(BOOL allow_keepalive, int keepalive_flags)
{
	time_t t, chk;

	/* On the receiving side, the generator is now the one that decides
	 * when a timeout has occurred.  When it is sifting through a lot of
	 * files looking for work, it will be sending keep-alive messages to
	 * the sender, and even though the receiver won't be sending/receiving
	 * anything (not even keep-alive messages), the successful writes to
	 * the sender will keep things going.  If the receiver is actively
	 * receiving data, it will ensure that the generator knows that it is
	 * not idle by sending the generator keep-alive messages (since the
	 * generator might be blocked trying to send checksums, it needs to
	 * know that the receiver is active).  Thus, as long as one or the
	 * other is successfully doing work, the generator will not timeout. */
	if (!io_timeout)
		return;

	t = time(NULL);

	if (allow_keepalive) {
		/* This may put data into iobuf.msg w/o flushing. */
		maybe_send_keepalive(t, keepalive_flags);
	}

	if (!last_io_in)
		last_io_in = t;

	if (am_receiver)
		return;

	chk = MAX(last_io_out, last_io_in);
	if (t - chk >= io_timeout) {
		if (am_server)
			msgs2stderr = 1;
		rprintf(FERROR, "[%s] io timeout after %d seconds -- exiting\n",
			who_am_i(), (int)(t-chk));
		exit_cleanup(RERR_TIMEOUT);
	}
}

/* It's almost always an error to get an EOF when we're trying to read from the
 * network, because the protocol is (for the most part) self-terminating.
 *
 * There is one case for the receiver when it is at the end of the transfer
 * (hanging around reading any keep-alive packets that might come its way): if
 * the sender dies before the generator's kill-signal comes through, we can end
 * up here needing to loop until the kill-signal arrives.  In this situation,
 * kluge_around_eof will be < 0.
 *
 * There is another case for older protocol versions (< 24) where the module
 * listing was not terminated, so we must ignore an EOF error in that case and
 * exit.  In this situation, kluge_around_eof will be > 0. */
static NORETURN void whine_about_eof(BOOL allow_kluge)
{
	if (kluge_around_eof && allow_kluge) {
		int i;
		if (kluge_around_eof > 0)
			exit_cleanup(0);
		/* If we're still here after 10 seconds, exit with an error. */
		for (i = 10*1000/20; i--; )
			msleep(20);
	}

	rprintf(FERROR, RSYNC_NAME ": connection unexpectedly closed "
		"(%s bytes received so far) [%s]\n",
		big_num(stats.total_read), who_am_i());

	exit_cleanup(RERR_STREAMIO);
}

/* Do a safe read, handling any needed looping and error handling.
 * Returns the count of the bytes read, which will only be different
 * from "len" if we encountered an EOF.  This routine is not used on
 * the socket except very early in the transfer. */
static size_t safe_read(int fd, char *buf, size_t len)
{
	size_t got = 0;

	assert(fd != iobuf.in_fd);

	while (1) {
		struct timeval tv;
		fd_set r_fds, e_fds;
		int cnt;

		FD_ZERO(&r_fds);
		FD_SET(fd, &r_fds);
		FD_ZERO(&e_fds);
		FD_SET(fd, &e_fds);
		tv.tv_sec = select_timeout;
		tv.tv_usec = 0;

		cnt = select(fd+1, &r_fds, NULL, &e_fds, &tv);
		if (cnt <= 0) {
			if (cnt < 0 && errno == EBADF) {
				rsyserr(FERROR, errno, "safe_read select failed");
				exit_cleanup(RERR_FILEIO);
			}
			check_timeout(1, MSK_ALLOW_FLUSH);
			continue;
		}

		/*if (FD_ISSET(fd, &e_fds))
			rprintf(FINFO, "select exception on fd %d\n", fd); */

		if (FD_ISSET(fd, &r_fds)) {
			ssize_t n = read(fd, buf + got, len - got);
			if (DEBUG_GTE(IO, 2)) {
				rprintf(FINFO, "[%s] safe_read(%d)=%" SIZE_T_FMT_MOD "d\n",
					who_am_i(), fd, (SIZE_T_FMT_CAST)n);
			}
			if (n == 0)
				break;
			if (n < 0) {
				if (errno == EINTR)
					continue;
				rsyserr(FERROR, errno, "safe_read failed to read %" SIZE_T_FMT_MOD "d bytes",
					(SIZE_T_FMT_CAST)len);
				exit_cleanup(RERR_STREAMIO);
			}
			if ((got += (size_t)n) == len)
				break;
		}
	}

	return got;
}

static const char *what_fd_is(int fd)
{
	static char buf[20];

	if (fd == sock_f_out)
		return "socket";
	else if (fd == iobuf.out_fd)
		return "message fd";
	else if (fd == batch_fd)
		return "batch file";
	else {
		snprintf(buf, sizeof buf, "fd %d", fd);
		return buf;
	}
}

/* Do a safe write, handling any needed looping and error handling.
 * Returns only if everything was successfully written.  This routine
 * is not used on the socket except very early in the transfer. */
static void safe_write(int fd, const char *buf, size_t len)
{
	ssize_t n;

	assert(fd != iobuf.out_fd);

	n = write(fd, buf, len);
	if ((size_t)n == len)
		return;
	if (n < 0) {
		if (errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN) {
		  write_failed:
			rsyserr(FERROR, errno,
				"safe_write failed to write %" SIZE_T_FMT_MOD "d bytes to %s",
				(SIZE_T_FMT_CAST)len, what_fd_is(fd));
			exit_cleanup(RERR_STREAMIO);
		}
	} else {
		buf += n;
		len -= n;
	}

	while (len) {
		struct timeval tv;
		fd_set w_fds;
		int cnt;

		FD_ZERO(&w_fds);
		FD_SET(fd, &w_fds);
		tv.tv_sec = select_timeout;
		tv.tv_usec = 0;

		cnt = select(fd + 1, NULL, &w_fds, NULL, &tv);
		if (cnt <= 0) {
			if (cnt < 0 && errno == EBADF) {
				rsyserr(FERROR, errno, "safe_write select failed on %s", what_fd_is(fd));
				exit_cleanup(RERR_FILEIO);
			}
			if (io_timeout)
				maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH);
			continue;
		}

		if (FD_ISSET(fd, &w_fds)) {
			n = write(fd, buf, len);
			if (n < 0) {
				if (errno == EINTR)
					continue;
				goto write_failed;
			}
			buf += n;
			len -= n;
		}
	}
}

/* This is only called when files-from data is known to be available.  We read
 * a chunk of data and put it into the output buffer. */
static void forward_filesfrom_data(void)
{
	ssize_t len;

	len = read(ff_forward_fd, ff_xb.buf + ff_xb.len, ff_xb.size - ff_xb.len);
	if (len <= 0) {
		if (len == 0 || errno != EINTR) {
			/* Send end-of-file marker */
			ff_forward_fd = -1;
			write_buf(iobuf.out_fd, "\0\0", ff_lastchar ? 2 : 1);
			free_xbuf(&ff_xb);
			if (ff_reenable_multiplex >= 0)
				io_start_multiplex_out(ff_reenable_multiplex);
			free_implied_include_partial_string();
		}
		return;
	}

	if (DEBUG_GTE(IO, 2)) {
		rprintf(FINFO, "[%s] files-from read=%" SIZE_T_FMT_MOD "d\n",
			who_am_i(), (SIZE_T_FMT_CAST)len);
	}

#ifdef ICONV_OPTION
	len += ff_xb.len;
#endif

	if (!eol_nulls) {
		char *s = ff_xb.buf + len;
		/* Transform CR and/or LF into '\0' */
		while (s-- > ff_xb.buf) {
			if (*s == '\n' || *s == '\r')
				*s = '\0';
		}
	}

	if (ff_lastchar)
		ff_xb.pos = 0;
	else {
		char *s = ff_xb.buf;
		/* Last buf ended with a '\0', so don't let this buf start with one. */
		while (len && *s == '\0')
			s++, len--;
		ff_xb.pos = s - ff_xb.buf;
	}

#ifdef ICONV_OPTION
	if (filesfrom_convert && len) {
		char *sob = ff_xb.buf + ff_xb.pos, *s = sob;
		char *eob = sob + len;
		int flags = ICB_INCLUDE_BAD | ICB_INCLUDE_INCOMPLETE | ICB_CIRCULAR_OUT;
		if (ff_lastchar == '\0')
			flags |= ICB_INIT;
		/* Convert/send each null-terminated string separately, skipping empties. */
		while (s != eob) {
			if (*s++ == '\0') {
				ff_xb.len = s - sob - 1;
				add_implied_include(sob, 0);
				if (iconvbufs(ic_send, &ff_xb, &iobuf.out, flags) < 0)
					exit_cleanup(RERR_PROTOCOL); /* impossible? */
				write_buf(iobuf.out_fd, s-1, 1); /* Send the '\0'. */
				while (s != eob && *s == '\0')
					s++;
				sob = s;
				ff_xb.pos = sob - ff_xb.buf;
				flags |= ICB_INIT;
			}
		}

		if ((ff_xb.len = s - sob) == 0)
			ff_lastchar = '\0';
		else {
			/* Handle a partial string specially, saving any incomplete chars. */
			implied_include_partial_string(sob, s);
			flags &= ~ICB_INCLUDE_INCOMPLETE;
			if (iconvbufs(ic_send, &ff_xb, &iobuf.out, flags) < 0) {
				if (errno == E2BIG)
					exit_cleanup(RERR_PROTOCOL); /* impossible? */
				if (ff_xb.pos)
					memmove(ff_xb.buf, ff_xb.buf + ff_xb.pos, ff_xb.len);
			}
			ff_lastchar = 'x'; /* Anything non-zero. */
		}
	} else
#endif

	if (len) {
		char *f = ff_xb.buf + ff_xb.pos;
		char *t = ff_xb.buf;
		char *eob = f + len;
		char *cur = t;
		/* Eliminate any multi-'\0' runs. */
		while (f != eob) {
			if (!(*t++ = *f++)) {
				add_implied_include(cur, 0);
				cur = t;
				while (f != eob && *f == '\0')
					f++;
			}
		}
		implied_include_partial_string(cur, t);
		ff_lastchar = f[-1];
		if ((len = t - ff_xb.buf) != 0) {
			/* This will not circle back to perform_io() because we only get
			 * called when there is plenty of room in the output buffer. */
			write_buf(iobuf.out_fd, ff_xb.buf, len);
		}
	}
}

void reduce_iobuf_size(xbuf *out, size_t new_size)
{
	if (new_size < out->size) {
		/* Avoid weird buffer interactions by only outputting this to stderr. */
		if (msgs2stderr == 1 && DEBUG_GTE(IO, 4)) {
			const char *name = out == &iobuf.out ? "iobuf.out"
					 : out == &iobuf.msg ? "iobuf.msg"
					 : NULL;
			if (name) {
				rprintf(FINFO, "[%s] reduced size of %s (-%d)\n",
					who_am_i(), name, (int)(out->size - new_size));
			}
		}
		out->size = new_size;
	}
}

void restore_iobuf_size(xbuf *out)
{
	if (IOBUF_WAS_REDUCED(out->size)) {
		size_t new_size = IOBUF_RESTORE_SIZE(out->size);
		/* Avoid weird buffer interactions by only outputting this to stderr. */
		if (msgs2stderr == 1 && DEBUG_GTE(IO, 4)) {
			const char *name = out == &iobuf.out ? "iobuf.out"
					 : out == &iobuf.msg ? "iobuf.msg"
					 : NULL;
			if (name) {
				rprintf(FINFO, "[%s] restored size of %s (+%d)\n",
					who_am_i(), name, (int)(new_size - out->size));
			}
		}
		out->size = new_size;
	}
}

static void handle_kill_signal(BOOL flush_ok)
{
	got_kill_signal = -1;
	flush_ok_after_signal = flush_ok;
	exit_cleanup(RERR_SIGNAL);
}

/* Perform buffered input and/or output until specified conditions are met.
 * When given a "needed" read or write request, this returns without doing any
 * I/O if the needed input bytes or write space is already available.  Once I/O
 * is needed, this will try to do whatever reading and/or writing is currently
 * possible, up to the maximum buffer allowances, no matter if this is a read
 * or write request.  However, the I/O stops as soon as the required input
 * bytes or output space is available.  If this is not a read request, the
 * routine may also do some advantageous reading of messages from a multiplexed
 * input source (which ensures that we don't jam up with everyone in their
 * "need to write" code and nobody reading the accumulated data that would make
 * writing possible).
 *
 * The iobuf.in, .out and .msg buffers are all circular.  Callers need to be
 * aware that some data copies will need to be split when the bytes wrap around
 * from the end to the start.  In order to help make writing into the output
 * buffers easier for some operations (such as the use of SIVAL() into the
 * buffer) a buffer may be temporarily shortened by a small amount, but the
 * original size will be automatically restored when the .pos wraps to the
 * start.  See also the 3 raw_* iobuf vars that are used in the handling of
 * MSG_DATA bytes as they are read-from/written-into the buffers.
 *
 * When writing, we flush data in the following priority order:
 *
 * 1. Finish writing any in-progress MSG_DATA sequence from iobuf.out.
 *
 * 2. Write out all the messages from the message buf (if iobuf.msg is active).
 *    Yes, this means that a PIO_NEED_OUTROOM call will completely flush any
 *    messages before getting to the iobuf.out flushing (except for rule 1).
 *
 * 3. Write out the raw data from iobuf.out, possibly filling in the multiplexed
 *    MSG_DATA header that was pre-allocated (when output is multiplexed).
 *
 * TODO:  items for possible future work:
 *
 *    - Make this routine able to read the generator-to-receiver batch flow?
 *
 * Unlike the old routines that this replaces, it is OK to read ahead as far as
 * we can because the read_a_msg() routine now reads its bytes out of the input
 * buffer.  In the old days, only raw data was in the input buffer, and any
 * unused raw data in the buf would prevent the reading of socket data. */
static char *perform_io(size_t needed, int flags)
{
	fd_set r_fds, e_fds, w_fds;
	struct timeval tv;
	int cnt, max_fd;
	size_t empty_buf_len = 0;
	xbuf *out;
	char *data;

	if (iobuf.in.len == 0 && iobuf.in.pos != 0) {
		if (iobuf.raw_input_ends_before)
			iobuf.raw_input_ends_before -= iobuf.in.pos;
		iobuf.in.pos = 0;
	}

	switch (flags & PIO_NEED_FLAGS) {
	case PIO_NEED_INPUT:
		/* We never resize the circular input buffer. */
		if (iobuf.in.size < needed) {
			rprintf(FERROR, "need to read %" SIZE_T_FMT_MOD "d bytes,"
					" iobuf.in.buf is only %" SIZE_T_FMT_MOD "d bytes.\n",
				(SIZE_T_FMT_CAST)needed, (SIZE_T_FMT_CAST)iobuf.in.size);
			exit_cleanup(RERR_PROTOCOL);
		}

		if (msgs2stderr == 1 && DEBUG_GTE(IO, 3)) {
			rprintf(FINFO, "[%s] perform_io(%" SIZE_T_FMT_MOD "d, %sinput)\n",
				who_am_i(), (SIZE_T_FMT_CAST)needed, flags & PIO_CONSUME_INPUT ? "consume&" : "");
		}
		break;

	case PIO_NEED_OUTROOM:
		/* We never resize the circular output buffer. */
		if (iobuf.out.size - iobuf.out_empty_len < needed) {
			fprintf(stderr, "need to write %" SIZE_T_FMT_MOD "d bytes,"
					" iobuf.out.buf is only %" SIZE_T_FMT_MOD "d bytes.\n",
				(SIZE_T_FMT_CAST)needed, (SIZE_T_FMT_CAST)(iobuf.out.size - iobuf.out_empty_len));
			exit_cleanup(RERR_PROTOCOL);
		}

		if (msgs2stderr == 1 && DEBUG_GTE(IO, 3)) {
			rprintf(FINFO, "[%s] perform_io(%" SIZE_T_FMT_MOD "d,"
				       " outroom) needs to flush %" SIZE_T_FMT_MOD "d\n",
				who_am_i(), (SIZE_T_FMT_CAST)needed,
				iobuf.out.len + needed > iobuf.out.size
				? (SIZE_T_FMT_CAST)(iobuf.out.len + needed - iobuf.out.size) : (SIZE_T_FMT_CAST)0);
		}
		break;

	case PIO_NEED_MSGROOM:
		/* We never resize the circular message buffer. */
		if (iobuf.msg.size < needed) {
			fprintf(stderr, "need to write %" SIZE_T_FMT_MOD "d bytes,"
					" iobuf.msg.buf is only %" SIZE_T_FMT_MOD "d bytes.\n",
				(SIZE_T_FMT_CAST)needed, (SIZE_T_FMT_CAST)iobuf.msg.size);
			exit_cleanup(RERR_PROTOCOL);
		}

		if (msgs2stderr == 1 && DEBUG_GTE(IO, 3)) {
			rprintf(FINFO, "[%s] perform_io(%" SIZE_T_FMT_MOD "d,"
				       " msgroom) needs to flush %" SIZE_T_FMT_MOD "d\n",
				who_am_i(), (SIZE_T_FMT_CAST)needed,
				iobuf.msg.len + needed > iobuf.msg.size
				? (SIZE_T_FMT_CAST)(iobuf.msg.len + needed - iobuf.msg.size) : (SIZE_T_FMT_CAST)0);
		}
		break;

	case 0:
		if (msgs2stderr == 1 && DEBUG_GTE(IO, 3)) {
			rprintf(FINFO, "[%s] perform_io(%" SIZE_T_FMT_MOD "d, %d)\n",
				who_am_i(), (SIZE_T_FMT_CAST)needed, flags);
		}
		break;

	default:
		exit_cleanup(RERR_UNSUPPORTED);
	}

	while (1) {
		switch (flags & PIO_NEED_FLAGS) {
		case PIO_NEED_INPUT:
			if (iobuf.in.len >= needed)
				goto double_break;
			break;
		case PIO_NEED_OUTROOM:
			/* Note that iobuf.out_empty_len doesn't factor into this check
			 * because iobuf.out.len already holds any needed header len. */
			if (iobuf.out.len + needed <= iobuf.out.size)
				goto double_break;
			break;
		case PIO_NEED_MSGROOM:
			if (iobuf.msg.len + needed <= iobuf.msg.size)
				goto double_break;
			break;
		}

		max_fd = -1;

		FD_ZERO(&r_fds);
		FD_ZERO(&e_fds);
		if (iobuf.in_fd >= 0 && iobuf.in.size - iobuf.in.len) {
			if (!read_batch || batch_fd >= 0) {
				FD_SET(iobuf.in_fd, &r_fds);
				FD_SET(iobuf.in_fd, &e_fds);
			}
			if (iobuf.in_fd > max_fd)
				max_fd = iobuf.in_fd;
		}

		/* Only do more filesfrom processing if there is enough room in the out buffer. */
		if (ff_forward_fd >= 0 && iobuf.out.size - iobuf.out.len > FILESFROM_BUFLEN*2) {
			FD_SET(ff_forward_fd, &r_fds);
			if (ff_forward_fd > max_fd)
				max_fd = ff_forward_fd;
		}

		FD_ZERO(&w_fds);
		if (iobuf.out_fd >= 0) {
			if (iobuf.raw_flushing_ends_before
			 || (!iobuf.msg.len && iobuf.out.len > iobuf.out_empty_len && !(flags & PIO_NEED_MSGROOM))) {
				if (OUT_MULTIPLEXED && !iobuf.raw_flushing_ends_before) {
					/* The iobuf.raw_flushing_ends_before value can point off the end
					 * of the iobuf.out buffer for a while, for easier subtracting. */
					iobuf.raw_flushing_ends_before = iobuf.out.pos + iobuf.out.len;

					SIVAL(iobuf.out.buf + iobuf.raw_data_header_pos, 0,
					      ((MPLEX_BASE + (int)MSG_DATA)<<24) + iobuf.out.len - 4);

					if (msgs2stderr == 1 && DEBUG_GTE(IO, 1)) {
						rprintf(FINFO, "[%s] send_msg(%d, %" SIZE_T_FMT_MOD "d)\n",
							who_am_i(), (int)MSG_DATA, (SIZE_T_FMT_CAST)iobuf.out.len - 4);
					}

					/* reserve room for the next MSG_DATA header */
					iobuf.raw_data_header_pos = iobuf.raw_flushing_ends_before;
					if (iobuf.raw_data_header_pos >= iobuf.out.size)
						iobuf.raw_data_header_pos -= iobuf.out.size;
					else if (iobuf.raw_data_header_pos + 4 > iobuf.out.size) {
						/* The 4-byte header won't fit at the end of the buffer,
						 * so we'll temporarily reduce the output buffer's size
						 * and put the header at the start of the buffer. */
						reduce_iobuf_size(&iobuf.out, iobuf.raw_data_header_pos);
						iobuf.raw_data_header_pos = 0;
					}
					/* Yes, it is possible for this to make len > size for a while. */
					iobuf.out.len += 4;
				}

				empty_buf_len = iobuf.out_empty_len;
				out = &iobuf.out;
			} else if (iobuf.msg.len) {
				empty_buf_len = 0;
				out = &iobuf.msg;
			} else
				out = NULL;
			if (out) {
				FD_SET(iobuf.out_fd, &w_fds);
				if (iobuf.out_fd > max_fd)
					max_fd = iobuf.out_fd;
			}
		} else
			out = NULL;

		if (max_fd < 0) {
			switch (flags & PIO_NEED_FLAGS) {
			case PIO_NEED_INPUT:
				iobuf.in.len = 0;
				if (kluge_around_eof == 2)
					exit_cleanup(0);
				if (iobuf.in_fd == -2)
					whine_about_eof(True);
				rprintf(FERROR, "error in perform_io: no fd for input.\n");
				exit_cleanup(RERR_PROTOCOL);
			case PIO_NEED_OUTROOM:
			case PIO_NEED_MSGROOM:
				msgs2stderr = 1;
				drain_multiplex_messages();
				if (iobuf.out_fd == -2)
					whine_about_eof(True);
				rprintf(FERROR, "error in perform_io: no fd for output.\n");
				exit_cleanup(RERR_PROTOCOL);
			default:
				/* No stated needs, so I guess this is OK. */
				break;
			}
			break;
		}

		if (got_kill_signal > 0)
			handle_kill_signal(True);

		if (extra_flist_sending_enabled) {
			if (file_total - file_old_total < MAX_FILECNT_LOOKAHEAD && IN_MULTIPLEXED_AND_READY)
				tv.tv_sec = 0;
			else {
				extra_flist_sending_enabled = False;
				tv.tv_sec = select_timeout;
			}
		} else
			tv.tv_sec = select_timeout;
		tv.tv_usec = 0;

		cnt = select(max_fd + 1, &r_fds, &w_fds, &e_fds, &tv);

		if (cnt <= 0) {
			if (cnt < 0 && errno == EBADF) {
				msgs2stderr = 1;
				exit_cleanup(RERR_SOCKETIO);
			}
			if (extra_flist_sending_enabled) {
				extra_flist_sending_enabled = False;
				send_extra_file_list(sock_f_out, -1);
				extra_flist_sending_enabled = !flist_eof;
			} else
				check_timeout((flags & PIO_NEED_INPUT) != 0, 0);
			FD_ZERO(&r_fds); /* Just in case... */
			FD_ZERO(&w_fds);
		}

		if (iobuf.in_fd >= 0 && FD_ISSET(iobuf.in_fd, &r_fds)) {
			size_t len, pos = iobuf.in.pos + iobuf.in.len;
			ssize_t n;
			if (pos >= iobuf.in.size) {
				pos -= iobuf.in.size;
				len = iobuf.in.size - iobuf.in.len;
			} else
				len = iobuf.in.size - pos;
			if ((n = read(iobuf.in_fd, iobuf.in.buf + pos, len)) <= 0) {
				if (n == 0) {
					/* Signal that input has become invalid. */
					if (!read_batch || batch_fd < 0 || am_generator)
						iobuf.in_fd = -2;
					batch_fd = -1;
					continue;
				}
				if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)
					n = 0;
				else {
					/* Don't write errors on a dead socket. */
					if (iobuf.in_fd == sock_f_in) {
						if (am_sender)
							msgs2stderr = 1;
						rsyserr(FERROR_SOCKET, errno, "read error");
					} else
						rsyserr(FERROR, errno, "read error");
					exit_cleanup(RERR_SOCKETIO);
				}
			}
			if (msgs2stderr == 1 && DEBUG_GTE(IO, 2)) {
				rprintf(FINFO, "[%s] recv=%" SIZE_T_FMT_MOD "d\n",
					who_am_i(), (SIZE_T_FMT_CAST)n);
			}

			if (io_timeout) {
				last_io_in = time(NULL);
				if (io_timeout && flags & PIO_NEED_INPUT)
					maybe_send_keepalive(last_io_in, 0);
			}
			stats.total_read += n;

			iobuf.in.len += n;
		}

		if (stop_at_utime && time(NULL) >= stop_at_utime) {
			rprintf(FERROR, "stopping at requested limit\n");
			exit_cleanup(RERR_TIMEOUT);
		}

		if (out && FD_ISSET(iobuf.out_fd, &w_fds)) {
			size_t len = iobuf.raw_flushing_ends_before ? iobuf.raw_flushing_ends_before - out->pos : out->len;
			ssize_t n;

			if (bwlimit_writemax && len > bwlimit_writemax)
				len = bwlimit_writemax;

			if (out->pos + len > out->size)
				len = out->size - out->pos;
			if ((n = write(iobuf.out_fd, out->buf + out->pos, len)) <= 0) {
				if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)
					n = 0;
				else {
					/* Don't write errors on a dead socket. */
					msgs2stderr = 1;
					iobuf.out_fd = -2;
					iobuf.out.len = iobuf.msg.len = iobuf.raw_flushing_ends_before = 0;
					rsyserr(FERROR_SOCKET, errno, "write error");
					drain_multiplex_messages();
					exit_cleanup(RERR_SOCKETIO);
				}
			}
			if (msgs2stderr == 1 && DEBUG_GTE(IO, 2)) {
				rprintf(FINFO, "[%s] %s sent=%" SIZE_T_FMT_MOD "d\n",
					who_am_i(), out == &iobuf.out ? "out" : "msg", (SIZE_T_FMT_CAST)n);
			}

			if (io_timeout)
				last_io_out = time(NULL);
			stats.total_written += n;

			if (bwlimit_writemax)
				sleep_for_bwlimit(n);

			if ((out->pos += n) == out->size) {
				if (iobuf.raw_flushing_ends_before)
					iobuf.raw_flushing_ends_before -= out->size;
				out->pos = 0;
				restore_iobuf_size(out);
			} else if (out->pos == iobuf.raw_flushing_ends_before)
				iobuf.raw_flushing_ends_before = 0;
			if ((out->len -= n) == empty_buf_len) {
				out->pos = 0;
				restore_iobuf_size(out);
				if (empty_buf_len)
					iobuf.raw_data_header_pos = 0;
			}
		}

		if (got_kill_signal > 0)
			handle_kill_signal(True);

		/* We need to help prevent deadlock by doing what reading
		 * we can whenever we are here trying to write. */
		if (IN_MULTIPLEXED_AND_READY && !(flags & PIO_NEED_INPUT)) {
			while (!iobuf.raw_input_ends_before && iobuf.in.len > 512)
				read_a_msg();
			if (flist_receiving_enabled && iobuf.in.len > 512)
				wait_for_receiver(); /* generator only */
		}

		if (ff_forward_fd >= 0 && FD_ISSET(ff_forward_fd, &r_fds)) {
			/* This can potentially flush all output and enable
			 * multiplexed output, so keep this last in the loop
			 * and be sure to not cache anything that would break
			 * such a change. */
			forward_filesfrom_data();
		}
	}
  double_break:

	if (got_kill_signal > 0)
		handle_kill_signal(True);

	data = iobuf.in.buf + iobuf.in.pos;

	if (flags & PIO_CONSUME_INPUT) {
		iobuf.in.len -= needed;
		iobuf.in.pos += needed;
		if (iobuf.in.pos == iobuf.raw_input_ends_before)
			iobuf.raw_input_ends_before = 0;
		if (iobuf.in.pos >= iobuf.in.size) {
			iobuf.in.pos -= iobuf.in.size;
			if (iobuf.raw_input_ends_before)
				iobuf.raw_input_ends_before -= iobuf.in.size;
		}
	}

	return data;
}

static void raw_read_buf(char *buf, size_t len)
{
	size_t pos = iobuf.in.pos;
	char *data = perform_io(len, PIO_INPUT_AND_CONSUME);
	if (iobuf.in.pos <= pos && len) {
		size_t siz = len - iobuf.in.pos;
		memcpy(buf, data, siz);
		memcpy(buf + siz, iobuf.in.buf, iobuf.in.pos);
	} else
		memcpy(buf, data, len);
}

static int32 raw_read_int(void)
{
	char *data, buf[4];
	if (iobuf.in.size - iobuf.in.pos >= 4)
		data = perform_io(4, PIO_INPUT_AND_CONSUME);
	else
		raw_read_buf(data = buf, 4);
	return IVAL(data, 0);
}

void noop_io_until_death(void)
{
	char buf[1024];

	if (!iobuf.in.buf || !iobuf.out.buf || iobuf.in_fd < 0 || iobuf.out_fd < 0 || kluge_around_eof)
		return;

	/* If we're talking to a daemon over a socket, don't short-circuit this logic */
	if (msgs2stderr && daemon_connection >= 0)
		return;

	kluge_around_eof = 2;
	/* Setting an I/O timeout ensures that if something inexplicably weird
	 * happens, we won't hang around forever. */
	if (!io_timeout)
		set_io_timeout(60);

	while (1)
		read_buf(iobuf.in_fd, buf, sizeof buf);
}

/* Buffer a message for the multiplexed output stream.  Is not used for (normal) MSG_DATA. */
int send_msg(enum msgcode code, const char *buf, size_t len, int convert)
{
	char *hdr;
	size_t needed, pos;
	BOOL want_debug = DEBUG_GTE(IO, 1) && convert >= 0 && (msgs2stderr == 1 || code != MSG_INFO);

	if (!OUT_MULTIPLEXED)
		return 0;

	if (want_debug) {
		rprintf(FINFO, "[%s] send_msg(%d, %" SIZE_T_FMT_MOD "d)\n",
			who_am_i(), (int)code, (SIZE_T_FMT_CAST)len);
	}

	/* When checking for enough free space for this message, we need to
	 * make sure that there is space for the 4-byte header, plus we'll
	 * assume that we may waste up to 3 bytes (if the header doesn't fit
	 * at the physical end of the buffer). */
#ifdef ICONV_OPTION
	if (convert > 0 && ic_send == (iconv_t)-1)
		convert = 0;
	if (convert > 0) {
		/* Ensuring double-size room leaves space for maximal conversion expansion. */
		needed = len*2 + 4 + 3;
	} else
#endif
		needed = len + 4 + 3;
	if (iobuf.msg.len + needed > iobuf.msg.size) {
		if (am_sender)
			perform_io(needed, PIO_NEED_MSGROOM);
		else { /* We sometimes allow the iobuf.msg size to increase to avoid a deadlock. */
			size_t old_size = iobuf.msg.size;
			restore_iobuf_size(&iobuf.msg);
			realloc_xbuf(&iobuf.msg, iobuf.msg.size * 2);
			if (iobuf.msg.pos + iobuf.msg.len > old_size)
				memcpy(iobuf.msg.buf + old_size, iobuf.msg.buf, iobuf.msg.pos + iobuf.msg.len - old_size);
		}
	}

	pos = iobuf.msg.pos + iobuf.msg.len; /* Must be set after any flushing. */
	if (pos >= iobuf.msg.size)
		pos -= iobuf.msg.size;
	else if (pos + 4 > iobuf.msg.size) {
		/* The 4-byte header won't fit at the end of the buffer,
		 * so we'll temporarily reduce the message buffer's size
		 * and put the header at the start of the buffer. */
		reduce_iobuf_size(&iobuf.msg, pos);
		pos = 0;
	}
	hdr = iobuf.msg.buf + pos;

	iobuf.msg.len += 4; /* Allocate room for the coming header bytes. */

#ifdef ICONV_OPTION
	if (convert > 0) {
		xbuf inbuf;

		INIT_XBUF(inbuf, (char*)buf, len, (size_t)-1);

		len = iobuf.msg.len;
		iconvbufs(ic_send, &inbuf, &iobuf.msg,
			  ICB_INCLUDE_BAD | ICB_INCLUDE_INCOMPLETE | ICB_CIRCULAR_OUT | ICB_INIT);
		if (inbuf.len > 0) {
			rprintf(FERROR, "overflowed iobuf.msg buffer in send_msg");
			exit_cleanup(RERR_UNSUPPORTED);
		}
		len = iobuf.msg.len - len;
	} else
#endif
	{
		size_t siz;

		if ((pos += 4) == iobuf.msg.size)
			pos = 0;

		/* Handle a split copy if we wrap around the end of the circular buffer. */
		if (pos >= iobuf.msg.pos && (siz = iobuf.msg.size - pos) < len) {
			memcpy(iobuf.msg.buf + pos, buf, siz);
			memcpy(iobuf.msg.buf, buf + siz, len - siz);
		} else
			memcpy(iobuf.msg.buf + pos, buf, len);

		iobuf.msg.len += len;
	}

	SIVAL(hdr, 0, ((MPLEX_BASE + (int)code)<<24) + len);

	if (want_debug && convert > 0) {
		rprintf(FINFO, "[%s] converted msg len=%" SIZE_T_FMT_MOD "d\n",
			who_am_i(), (SIZE_T_FMT_CAST)len);
	}

	return 1;
}

void send_msg_int(enum msgcode code, int num)
{
	char numbuf[4];

	if (DEBUG_GTE(IO, 1))
		rprintf(FINFO, "[%s] send_msg_int(%d, %d)\n", who_am_i(), (int)code, num);

	SIVAL(numbuf, 0, num);
	send_msg(code, numbuf, 4, -1);
}

void send_msg_success(const char *fname, int num)
{
	if (local_server) {
		STRUCT_STAT st;

		if (DEBUG_GTE(IO, 1))
			rprintf(FINFO, "[%s] send_msg_success(%d)\n", who_am_i(), num);

		if (stat(fname, &st) < 0)
			memset(&st, 0, sizeof (STRUCT_STAT));
		SIVAL(num_dev_ino_buf, 0, num);
		SIVAL64(num_dev_ino_buf, 4, st.st_dev);
		SIVAL64(num_dev_ino_buf, 4+8, st.st_ino);
		send_msg(MSG_SUCCESS, num_dev_ino_buf, sizeof num_dev_ino_buf, -1);
	} else
		send_msg_int(MSG_SUCCESS, num);
}

static void got_flist_entry_status(enum festatus status, int ndx)
{
	struct file_list *flist = flist_for_ndx(ndx, "got_flist_entry_status");

	if (remove_source_files) {
		active_filecnt--;
		active_bytecnt -= F_LENGTH(flist->files[ndx - flist->ndx_start]);
	}

	if (inc_recurse)
		flist->in_progress--;

	switch (status) {
	case FES_SUCCESS:
		if (remove_source_files) {
			if (local_server)
				send_msg(MSG_SUCCESS, num_dev_ino_buf, sizeof num_dev_ino_buf, -1);
			else
				send_msg_int(MSG_SUCCESS, ndx);
		}
		/* FALL THROUGH */
	case FES_NO_SEND:
#ifdef SUPPORT_HARD_LINKS
		if (preserve_hard_links) {
			struct file_struct *file = flist->files[ndx - flist->ndx_start];
			if (F_IS_HLINKED(file)) {
				if (status == FES_NO_SEND)
					flist_ndx_push(&hlink_list, -2); /* indicates a failure follows */
				flist_ndx_push(&hlink_list, ndx);
				if (inc_recurse)
					flist->in_progress++;
			}
		}
#endif
		break;
	case FES_REDO:
		if (read_batch) {
			if (inc_recurse)
				flist->in_progress++;
			break;
		}
		if (inc_recurse)
			flist->to_redo++;
		flist_ndx_push(&redo_list, ndx);
		break;
	}
}

/* Note the fds used for the main socket (which might really be a pipe
 * for a local transfer, but we can ignore that). */
void io_set_sock_fds(int f_in, int f_out)
{
	sock_f_in = f_in;
	sock_f_out = f_out;
}

void set_io_timeout(int secs)
{
	io_timeout = secs;
	allowed_lull = (io_timeout + 1) / 2;

	if (!io_timeout || allowed_lull > SELECT_TIMEOUT)
		select_timeout = SELECT_TIMEOUT;
	else
		select_timeout = allowed_lull;

	if (read_batch)
		allowed_lull = 0;
}

static void check_for_d_option_error(const char *msg)
{
	static char rsync263_opts[] = "BCDHIKLPRSTWabceghlnopqrtuvxz";
	char *colon;
	int saw_d = 0;

	if (*msg != 'r'
	 || strncmp(msg, REMOTE_OPTION_ERROR, sizeof REMOTE_OPTION_ERROR - 1) != 0)
		return;

	msg += sizeof REMOTE_OPTION_ERROR - 1;
	if (*msg == '-' || (colon = strchr(msg, ':')) == NULL
	 || strncmp(colon, REMOTE_OPTION_ERROR2, sizeof REMOTE_OPTION_ERROR2 - 1) != 0)
		return;

	for ( ; *msg != ':'; msg++) {
		if (*msg == 'd')
			saw_d = 1;
		else if (*msg == 'e')
			break;
		else if (strchr(rsync263_opts, *msg) == NULL)
			return;
	}

	if (saw_d) {
		rprintf(FWARNING, "*** Try using \"--old-d\" if remote rsync is <= 2.6.3 ***\n");
	}
}

/* This is used by the generator to limit how many file transfers can
 * be active at once when --remove-source-files is specified.  Without
 * this, sender-side deletions were mostly happening at the end. */
void increment_active_files(int ndx, int itemizing, enum logcode code)
{
	while (1) {
		/* TODO: tune these limits? */
		int limit = active_bytecnt >= 128*1024 ? 10 : 50;
		if (active_filecnt < limit)
			break;
		check_for_finished_files(itemizing, code, 0);
		if (active_filecnt < limit)
			break;
		wait_for_receiver();
	}

	active_filecnt++;
	active_bytecnt += F_LENGTH(cur_flist->files[ndx - cur_flist->ndx_start]);
}

int get_redo_num(void)
{
	return flist_ndx_pop(&redo_list);
}

int get_hlink_num(void)
{
	return flist_ndx_pop(&hlink_list);
}

/* When we're the receiver and we have a local --files-from list of names
 * that needs to be sent over the socket to the sender, we have to do two
 * things at the same time: send the sender a list of what files we're
 * processing and read the incoming file+info list from the sender.  We do
 * this by making recv_file_list() call forward_filesfrom_data(), which
 * will ensure that we forward data to the sender until we get some data
 * for recv_file_list() to use. */
void start_filesfrom_forwarding(int fd)
{
	if (protocol_version < 31 && OUT_MULTIPLEXED) {
		/* Older protocols send the files-from data w/o packaging
		 * it in multiplexed I/O packets, so temporarily switch
		 * to buffered I/O to match this behavior. */
		iobuf.msg.pos = iobuf.msg.len = 0; /* Be extra sure no messages go out. */
		ff_reenable_multiplex = io_end_multiplex_out(MPLX_TO_BUFFERED);
	}
	ff_forward_fd = fd;

	alloc_xbuf(&ff_xb, FILESFROM_BUFLEN);
}

/* Read a line into the "buf" buffer. */
int read_line(int fd, char *buf, size_t bufsiz, int flags)
{
	char ch, *s, *eob;

#ifdef ICONV_OPTION
	if (flags & RL_CONVERT && iconv_buf.size < bufsiz)
		realloc_xbuf(&iconv_buf, ROUND_UP_1024(bufsiz) + 1024);
#endif

  start:
#ifdef ICONV_OPTION
	s = flags & RL_CONVERT ? iconv_buf.buf : buf;
#else
	s = buf;
#endif
	eob = s + bufsiz - 1;
	while (1) {
		/* We avoid read_byte() for files because files can return an EOF. */
		if (fd == iobuf.in_fd)
			ch = read_byte(fd);
		else if (safe_read(fd, &ch, 1) == 0)
			break;
		if (flags & RL_EOL_NULLS ? ch == '\0' : (ch == '\r' || ch == '\n')) {
			/* Skip empty lines if dumping comments. */
			if (flags & RL_DUMP_COMMENTS && s == buf)
				continue;
			break;
		}
		if (s < eob)
			*s++ = ch;
	}
	*s = '\0';

	if (flags & RL_DUMP_COMMENTS && (*buf == '#' || *buf == ';'))
		goto start;

#ifdef ICONV_OPTION
	if (flags & RL_CONVERT) {
		xbuf outbuf;
		INIT_XBUF(outbuf, buf, 0, bufsiz);
		iconv_buf.pos = 0;
		iconv_buf.len = s - iconv_buf.buf;
		iconvbufs(ic_recv, &iconv_buf, &outbuf,
			  ICB_INCLUDE_BAD | ICB_INCLUDE_INCOMPLETE | ICB_INIT);
		outbuf.buf[outbuf.len] = '\0';
		return outbuf.len;
	}
#endif

	return s - buf;
}

void read_args(int f_in, char *mod_name, char *buf, size_t bufsiz, int rl_nulls,
	       char ***argv_p, int *argc_p, char **request_p)
{
	int maxargs = MAX_ARGS;
	int dot_pos = 0, argc = 0, request_len = 0;
	char **argv, *p;
	int rl_flags = (rl_nulls ? RL_EOL_NULLS : 0);

#ifdef ICONV_OPTION
	rl_flags |= (protect_args && ic_recv != (iconv_t)-1 ? RL_CONVERT : 0);
#endif

	argv = new_array(char *, maxargs);
	if (mod_name && !protect_args)
		argv[argc++] = "rsyncd";

	if (request_p)
		*request_p = NULL;

	while (1) {
		if (read_line(f_in, buf, bufsiz, rl_flags) == 0)
			break;

		if (argc == maxargs-1) {
			maxargs += MAX_ARGS;
			argv = realloc_array(argv, char *, maxargs);
		}

		if (dot_pos) {
			if (request_p && request_len < 1024) {
				int len = strlen(buf);
				if (request_len)
					request_p[0][request_len++] = ' ';
				*request_p = realloc_array(*request_p, char, request_len + len + 1);
				memcpy(*request_p + request_len, buf, len + 1);
				request_len += len;
			}
			if (mod_name)
				glob_expand_module(mod_name, buf, &argv, &argc, &maxargs);
			else
				glob_expand(buf, &argv, &argc, &maxargs);
		} else {
			p = strdup(buf);
			argv[argc++] = p;
			if (*p == '.' && p[1] == '\0')
				dot_pos = argc;
		}
	}
	argv[argc] = NULL;

	glob_expand(NULL, NULL, NULL, NULL);

	*argc_p = argc;
	*argv_p = argv;
}

BOOL io_start_buffering_out(int f_out)
{
	if (msgs2stderr == 1 && DEBUG_GTE(IO, 2))
		rprintf(FINFO, "[%s] io_start_buffering_out(%d)\n", who_am_i(), f_out);

	if (iobuf.out.buf) {
		if (iobuf.out_fd == -1)
			iobuf.out_fd = f_out;
		else
			assert(f_out == iobuf.out_fd);
		return False;
	}

	alloc_xbuf(&iobuf.out, ROUND_UP_1024(IO_BUFFER_SIZE * 2));
	iobuf.out_fd = f_out;

	return True;
}

BOOL io_start_buffering_in(int f_in)
{
	if (msgs2stderr == 1 && DEBUG_GTE(IO, 2))
		rprintf(FINFO, "[%s] io_start_buffering_in(%d)\n", who_am_i(), f_in);

	if (iobuf.in.buf) {
		if (iobuf.in_fd == -1)
			iobuf.in_fd = f_in;
		else
			assert(f_in == iobuf.in_fd);
		return False;
	}

	alloc_xbuf(&iobuf.in, ROUND_UP_1024(IO_BUFFER_SIZE));
	iobuf.in_fd = f_in;

	return True;
}

void io_end_buffering_in(BOOL free_buffers)
{
	if (msgs2stderr == 1 && DEBUG_GTE(IO, 2)) {
		rprintf(FINFO, "[%s] io_end_buffering_in(IOBUF_%s_BUFS)\n",
			who_am_i(), free_buffers ? "FREE" : "KEEP");
	}

	if (free_buffers)
		free_xbuf(&iobuf.in);
	else
		iobuf.in.pos = iobuf.in.len = 0;

	iobuf.in_fd = -1;
}

void io_end_buffering_out(BOOL free_buffers)
{
	if (msgs2stderr == 1 && DEBUG_GTE(IO, 2)) {
		rprintf(FINFO, "[%s] io_end_buffering_out(IOBUF_%s_BUFS)\n",
			who_am_i(), free_buffers ? "FREE" : "KEEP");
	}

	io_flush(FULL_FLUSH);

	if (free_buffers) {
		free_xbuf(&iobuf.out);
		free_xbuf(&iobuf.msg);
	}

	iobuf.out_fd = -1;
}

void maybe_flush_socket(int important)
{
	if (flist_eof && iobuf.out.buf && iobuf.out.len > iobuf.out_empty_len
	 && (important || time(NULL) - last_io_out >= 5))
		io_flush(NORMAL_FLUSH);
}

/* Older rsync versions used to send either a MSG_NOOP (protocol 30) or a
 * raw-data-based keep-alive (protocol 29), both of which implied forwarding of
 * the message through the sender.  Since the new timeout method does not need
 * any forwarding, we just send an empty MSG_DATA message, which works with all
 * rsync versions.  This avoids any message forwarding, and leaves the raw-data
 * stream alone (since we can never be quite sure if that stream is in the
 * right state for a keep-alive message). */
void maybe_send_keepalive(time_t now, int flags)
{
	if (flags & MSK_ACTIVE_RECEIVER)
		last_io_in = now; /* Fudge things when we're working hard on the files. */

	/* Early in the transfer (before the receiver forks) the receiving side doesn't
	 * care if it hasn't sent data in a while as long as it is receiving data (in
	 * fact, a pre-3.1.0 rsync would die if we tried to send it a keep alive during
	 * this time).  So, if we're an early-receiving proc, just return and let the
	 * incoming data determine if we timeout. */
	if (!am_sender && !am_receiver && !am_generator)
		return;

	if (now - last_io_out >= allowed_lull) {
		/* The receiver is special:  it only sends keep-alive messages if it is
		 * actively receiving data.  Otherwise, it lets the generator timeout. */
		if (am_receiver && now - last_io_in >= io_timeout)
			return;

		if (!iobuf.msg.len && iobuf.out.len == iobuf.out_empty_len)
			send_msg(MSG_DATA, "", 0, 0);
		if (!(flags & MSK_ALLOW_FLUSH)) {
			/* Let the caller worry about writing out the data. */
		} else if (iobuf.msg.len)
			perform_io(iobuf.msg.size - iobuf.msg.len + 1, PIO_NEED_MSGROOM);
		else if (iobuf.out.len > iobuf.out_empty_len)
			io_flush(NORMAL_FLUSH);
	}
}

void start_flist_forward(int ndx)
{
	write_int(iobuf.out_fd, ndx);
	forward_flist_data = 1;
}

void stop_flist_forward(void)
{
	forward_flist_data = 0;
}

/* Read a message from a multiplexed source. */
static void read_a_msg(void)
{
	char data[BIGPATHBUFLEN];
	int tag, val;
	size_t msg_bytes;

	/* This ensures that perform_io() does not try to do any message reading
	 * until we've read all of the data for this message.  We should also
	 * try to avoid calling things that will cause data to be written via
	 * perform_io() prior to this being reset to 1. */
	iobuf.in_multiplexed = -1;

	tag = raw_read_int();

	msg_bytes = tag & 0xFFFFFF;
	tag = (tag >> 24) - MPLEX_BASE;

	if (msgs2stderr == 1 && DEBUG_GTE(IO, 1)) {
		rprintf(FINFO, "[%s] got msg=%d, len=%" SIZE_T_FMT_MOD "d\n",
			who_am_i(), (int)tag, (SIZE_T_FMT_CAST)msg_bytes);
	}

	switch (tag) {
	case MSG_DATA:
		assert(iobuf.raw_input_ends_before == 0);
		/* Though this does not yet read the data, we do mark where in
		 * the buffer the msg data will end once it is read.  It is
		 * possible that this points off the end of the buffer, in
		 * which case the gradual reading of the input stream will
		 * cause this value to wrap around and eventually become real. */
		if (msg_bytes)
			iobuf.raw_input_ends_before = iobuf.in.pos + msg_bytes;
		iobuf.in_multiplexed = 1;
		break;
	case MSG_STATS:
		if (msg_bytes != sizeof stats.total_read || !am_generator)
			goto invalid_msg;
		raw_read_buf((char*)&stats.total_read, sizeof stats.total_read);
		iobuf.in_multiplexed = 1;
		break;
	case MSG_REDO:
		if (msg_bytes != 4 || !am_generator)
			goto invalid_msg;
		val = raw_read_int();
		iobuf.in_multiplexed = 1;
		got_flist_entry_status(FES_REDO, val);
		break;
	case MSG_IO_ERROR:
		if (msg_bytes != 4)
			goto invalid_msg;
		val = raw_read_int();
		iobuf.in_multiplexed = 1;
		io_error |= val;
		if (am_receiver)
			send_msg_int(MSG_IO_ERROR, val);
		break;
	case MSG_IO_TIMEOUT:
		if (msg_bytes != 4 || am_server || am_generator)
			goto invalid_msg;
		val = raw_read_int();
		iobuf.in_multiplexed = 1;
		if (!io_timeout || io_timeout > val) {
			if (INFO_GTE(MISC, 2))
				rprintf(FINFO, "Setting --timeout=%d to match server\n", val);
			set_io_timeout(val);
		}
		break;
	case MSG_NOOP:
		/* Support protocol-30 keep-alive method. */
		if (msg_bytes != 0)
			goto invalid_msg;
		iobuf.in_multiplexed = 1;
		if (am_sender)
			maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH);
		break;
	case MSG_DELETED:
		if (msg_bytes >= sizeof data)
			goto overflow;
		if (am_generator) {
			raw_read_buf(data, msg_bytes);
			iobuf.in_multiplexed = 1;
			send_msg(MSG_DELETED, data, msg_bytes, 1);
			break;
		}
#ifdef ICONV_OPTION
		if (ic_recv != (iconv_t)-1) {
			xbuf outbuf, inbuf;
			char ibuf[512];
			int add_null = 0;
			int flags = ICB_INCLUDE_BAD | ICB_INIT;

			INIT_CONST_XBUF(outbuf, data);
			INIT_XBUF(inbuf, ibuf, 0, (size_t)-1);

			while (msg_bytes) {
				size_t len = msg_bytes > sizeof ibuf - inbuf.len ? sizeof ibuf - inbuf.len : msg_bytes;
				raw_read_buf(ibuf + inbuf.len, len);
				inbuf.pos = 0;
				inbuf.len += len;
				if (!(msg_bytes -= len) && !ibuf[inbuf.len-1])
					inbuf.len--, add_null = 1;
				if (iconvbufs(ic_send, &inbuf, &outbuf, flags) < 0) {
					if (errno == E2BIG)
						goto overflow;
					/* Buffer ended with an incomplete char, so move the
					 * bytes to the start of the buffer and continue. */
					memmove(ibuf, ibuf + inbuf.pos, inbuf.len);
				}
				flags &= ~ICB_INIT;
			}
			if (add_null) {
				if (outbuf.len == outbuf.size)
					goto overflow;
				outbuf.buf[outbuf.len++] = '\0';
			}
			msg_bytes = outbuf.len;
		} else
#endif
			raw_read_buf(data, msg_bytes);
		iobuf.in_multiplexed = 1;
		/* A directory name was sent with the trailing null */
		if (msg_bytes > 0 && !data[msg_bytes-1])
			log_delete(data, S_IFDIR);
		else {
			data[msg_bytes] = '\0';
			log_delete(data, S_IFREG);
		}
		break;
	case MSG_SUCCESS:
		if (msg_bytes != (local_server ? 4+8+8 : 4)) {
		  invalid_msg:
			rprintf(FERROR, "invalid multi-message %d:%lu [%s%s]\n",
				tag, (unsigned long)msg_bytes, who_am_i(),
				inc_recurse ? "/inc" : "");
			exit_cleanup(RERR_STREAMIO);
		}
		raw_read_buf(num_dev_ino_buf, msg_bytes);
		val = IVAL(num_dev_ino_buf, 0);
		iobuf.in_multiplexed = 1;
		if (am_generator)
			got_flist_entry_status(FES_SUCCESS, val);
		else
			successful_send(val);
		break;
	case MSG_NO_SEND:
		if (msg_bytes != 4)
			goto invalid_msg;
		val = raw_read_int();
		iobuf.in_multiplexed = 1;
		if (am_generator)
			got_flist_entry_status(FES_NO_SEND, val);
		else
			send_msg_int(MSG_NO_SEND, val);
		break;
	case MSG_ERROR_SOCKET:
	case MSG_ERROR_UTF8:
	case MSG_CLIENT:
	case MSG_LOG:
		if (!am_generator)
			goto invalid_msg;
		if (tag == MSG_ERROR_SOCKET)
			msgs2stderr = 1;
		/* FALL THROUGH */
	case MSG_INFO:
	case MSG_ERROR:
	case MSG_ERROR_XFER:
	case MSG_WARNING:
		if (msg_bytes >= sizeof data) {
		    overflow:
			rprintf(FERROR,
				"multiplexing overflow %d:%lu [%s%s]\n",
				tag, (unsigned long)msg_bytes, who_am_i(),
				inc_recurse ? "/inc" : "");
			exit_cleanup(RERR_STREAMIO);
		}
		raw_read_buf(data, msg_bytes);
		/* We don't set in_multiplexed value back to 1 before writing this message
		 * because the write might loop back and read yet another message, over and
		 * over again, while waiting for room to put the message in the msg buffer. */
		rwrite((enum logcode)tag, data, msg_bytes, !am_generator);
		iobuf.in_multiplexed = 1;
		if (first_message) {
			if (list_only && !am_sender && tag == 1 && msg_bytes < sizeof data) {
				data[msg_bytes] = '\0';
				check_for_d_option_error(data);
			}
			first_message = 0;
		}
		break;
	case MSG_ERROR_EXIT:
		if (msg_bytes == 4)
			val = raw_read_int();
		else if (msg_bytes == 0)
			val = 0;
		else
			goto invalid_msg;
		iobuf.in_multiplexed = 1;
		if (DEBUG_GTE(EXIT, 3)) {
			rprintf(FINFO, "[%s] got MSG_ERROR_EXIT with %" SIZE_T_FMT_MOD "d bytes\n",
					who_am_i(), (SIZE_T_FMT_CAST)msg_bytes);
		}
		if (msg_bytes == 0) {
			if (!am_sender && !am_generator) {
				if (DEBUG_GTE(EXIT, 3)) {
					rprintf(FINFO, "[%s] sending MSG_ERROR_EXIT (len 0)\n",
						who_am_i());
				}
				send_msg(MSG_ERROR_EXIT, "", 0, 0);
				io_flush(FULL_FLUSH);
			}
		} else if (protocol_version >= 31) {
			if (am_generator || am_receiver) {
				if (DEBUG_GTE(EXIT, 3)) {
					rprintf(FINFO, "[%s] sending MSG_ERROR_EXIT with exit_code %d\n",
						who_am_i(), val);
				}
				send_msg_int(MSG_ERROR_EXIT, val);
			} else {
				if (DEBUG_GTE(EXIT, 3)) {
					rprintf(FINFO, "[%s] sending MSG_ERROR_EXIT (len 0)\n",
						who_am_i());
				}
				send_msg(MSG_ERROR_EXIT, "", 0, 0);
			}
		}
		/* Send a negative linenum so that we don't end up
		 * with a duplicate exit message. */
		_exit_cleanup(val, __FILE__, 0 - __LINE__);
	default:
		rprintf(FERROR, "unexpected tag %d [%s%s]\n",
			tag, who_am_i(), inc_recurse ? "/inc" : "");
		exit_cleanup(RERR_STREAMIO);
	}

	assert(iobuf.in_multiplexed > 0);
}

static void drain_multiplex_messages(void)
{
	while (IN_MULTIPLEXED_AND_READY && iobuf.in.len) {
		if (iobuf.raw_input_ends_before) {
			size_t raw_len = iobuf.raw_input_ends_before - iobuf.in.pos;
			iobuf.raw_input_ends_before = 0;
			if (raw_len >= iobuf.in.len) {
				iobuf.in.len = 0;
				break;
			}
			iobuf.in.len -= raw_len;
			if ((iobuf.in.pos += raw_len) >= iobuf.in.size)
				iobuf.in.pos -= iobuf.in.size;
		}
		read_a_msg();
	}
}

void wait_for_receiver(void)
{
	if (!iobuf.raw_input_ends_before)
		read_a_msg();

	if (iobuf.raw_input_ends_before) {
		int ndx = read_int(iobuf.in_fd);
		if (ndx < 0) {
			switch (ndx) {
			case NDX_FLIST_EOF:
				flist_eof = 1;
				if (DEBUG_GTE(FLIST, 3))
					rprintf(FINFO, "[%s] flist_eof=1\n", who_am_i());
				break;
			case NDX_DONE:
				msgdone_cnt++;
				break;
			default:
				exit_cleanup(RERR_STREAMIO);
			}
		} else {
			struct file_list *flist;
			flist_receiving_enabled = False;
			if (DEBUG_GTE(FLIST, 2)) {
				rprintf(FINFO, "[%s] receiving flist for dir %d\n",
					who_am_i(), ndx);
			}
			flist = recv_file_list(iobuf.in_fd, ndx);
			flist->parent_ndx = ndx;
#ifdef SUPPORT_HARD_LINKS
			if (preserve_hard_links)
				match_hard_links(flist);
#endif
			flist_receiving_enabled = True;
		}
	}
}

unsigned short read_shortint(int f)
{
	char b[2];
	read_buf(f, b, 2);
	return (UVAL(b, 1) << 8) + UVAL(b, 0);
}

int32 read_int(int f)
{
	char b[4];
	int32 num;

	read_buf(f, b, 4);
	num = IVAL(b, 0);
#if SIZEOF_INT32 > 4
	if (num & (int32)0x80000000)
		num |= ~(int32)0xffffffff;
#endif
	return num;
}

uint32 read_uint(int f)
{
	char b[4];
	read_buf(f, b, 4);
	return IVAL(b, 0);
}

int32 read_varint(int f)
{
	union {
		char b[5];
		int32 x;
	} u;
	uchar ch;
	int extra;

	u.x = 0;
	ch = read_byte(f);
	extra = int_byte_extra[ch / 4];
	if (extra) {
		uchar bit = ((uchar)1<<(8-extra));
		if (extra >= (int)sizeof u.b) {
			rprintf(FERROR, "Overflow in read_varint()\n");
			exit_cleanup(RERR_STREAMIO);
		}
		read_buf(f, u.b, extra);
		u.b[extra] = ch & (bit-1);
	} else
		u.b[0] = ch;
#if CAREFUL_ALIGNMENT
	u.x = IVAL(u.b,0);
#endif
#if SIZEOF_INT32 > 4
	if (u.x & (int32)0x80000000)
		u.x |= ~(int32)0xffffffff;
#endif
	return u.x;
}

int64 read_varlong(int f, uchar min_bytes)
{
	union {
		char b[9];
		int64 x;
	} u;
	char b2[8];
	int extra;

#if SIZEOF_INT64 < 8
	memset(u.b, 0, 8);
#else
	u.x = 0;
#endif
	read_buf(f, b2, min_bytes);
	memcpy(u.b, b2+1, min_bytes-1);
	extra = int_byte_extra[CVAL(b2, 0) / 4];
	if (extra) {
		uchar bit = ((uchar)1<<(8-extra));
		if (min_bytes + extra > (int)sizeof u.b) {
			rprintf(FERROR, "Overflow in read_varlong()\n");
			exit_cleanup(RERR_STREAMIO);
		}
		read_buf(f, u.b + min_bytes - 1, extra);
		u.b[min_bytes + extra - 1] = CVAL(b2, 0) & (bit-1);
#if SIZEOF_INT64 < 8
		if (min_bytes + extra > 5 || u.b[4] || CVAL(u.b,3) & 0x80) {
			rprintf(FERROR, "Integer overflow: attempted 64-bit offset\n");
			exit_cleanup(RERR_UNSUPPORTED);
		}
#endif
	} else
		u.b[min_bytes + extra - 1] = CVAL(b2, 0);
#if SIZEOF_INT64 < 8
	u.x = IVAL(u.b,0);
#elif CAREFUL_ALIGNMENT
	u.x = IVAL64(u.b,0);
#endif
	return u.x;
}

int64 read_longint(int f)
{
#if SIZEOF_INT64 >= 8
	char b[9];
#endif
	int32 num = read_int(f);

	if (num != (int32)0xffffffff)
		return num;

#if SIZEOF_INT64 < 8
	rprintf(FERROR, "Integer overflow: attempted 64-bit offset\n");
	exit_cleanup(RERR_UNSUPPORTED);
#else
	read_buf(f, b, 8);
	return IVAL(b,0) | (((int64)IVAL(b,4))<<32);
#endif
}

/* Debugging note: this will be named read_buf_() when using an external zlib. */
void read_buf(int f, char *buf, size_t len)
{
	if (f != iobuf.in_fd) {
		if (safe_read(f, buf, len) != len)
			whine_about_eof(False); /* Doesn't return. */
		goto batch_copy;
	}

	if (!IN_MULTIPLEXED) {
		raw_read_buf(buf, len);
		total_data_read += len;
		if (forward_flist_data)
			write_buf(iobuf.out_fd, buf, len);
	  batch_copy:
		if (f == write_batch_monitor_in)
			safe_write(batch_fd, buf, len);
		return;
	}

	while (1) {
		size_t siz;

		while (!iobuf.raw_input_ends_before)
			read_a_msg();

		siz = MIN(len, iobuf.raw_input_ends_before - iobuf.in.pos);
		if (siz >= iobuf.in.size)
			siz = iobuf.in.size;
		raw_read_buf(buf, siz);
		total_data_read += siz;

		if (forward_flist_data)
			write_buf(iobuf.out_fd, buf, siz);

		if (f == write_batch_monitor_in)
			safe_write(batch_fd, buf, siz);

		if ((len -= siz) == 0)
			break;
		buf += siz;
	}
}

void read_sbuf(int f, char *buf, size_t len)
{
	read_buf(f, buf, len);
	buf[len] = '\0';
}

uchar read_byte(int f)
{
	uchar c;
	read_buf(f, (char*)&c, 1);
	return c;
}

int read_vstring(int f, char *buf, int bufsize)
{
	int len = read_byte(f);

	if (len & 0x80)
		len = (len & ~0x80) * 0x100 + read_byte(f);

	if (len >= bufsize) {
		rprintf(FERROR, "over-long vstring received (%d > %d)\n",
			len, bufsize - 1);
		return -1;
	}

	if (len)
		read_buf(f, buf, len);
	buf[len] = '\0';
	return len;
}

/* Populate a sum_struct with values from the socket.  This is
 * called by both the sender and the receiver. */
void read_sum_head(int f, struct sum_struct *sum)
{
	int32 max_blength = protocol_version < 30 ? OLD_MAX_BLOCK_SIZE : MAX_BLOCK_SIZE;
	sum->count = read_int(f);
	if (sum->count < 0) {
		rprintf(FERROR, "Invalid checksum count %ld [%s]\n",
			(long)sum->count, who_am_i());
		exit_cleanup(RERR_PROTOCOL);
	}
	sum->blength = read_int(f);
	if (sum->blength < 0 || sum->blength > max_blength) {
		rprintf(FERROR, "Invalid block length %ld [%s]\n",
			(long)sum->blength, who_am_i());
		exit_cleanup(RERR_PROTOCOL);
	}
	sum->s2length = protocol_version < 27 ? csum_length : (int)read_int(f);
	if (sum->s2length < 0 || sum->s2length > xfer_sum_len) {
		rprintf(FERROR, "Invalid checksum length %d [%s]\n",
			sum->s2length, who_am_i());
		exit_cleanup(RERR_PROTOCOL);
	}
	sum->remainder = read_int(f);
	if (sum->remainder < 0 || sum->remainder > sum->blength) {
		rprintf(FERROR, "Invalid remainder length %ld [%s]\n",
			(long)sum->remainder, who_am_i());
		exit_cleanup(RERR_PROTOCOL);
	}
}

/* Send the values from a sum_struct over the socket.  Set sum to
 * NULL if there are no checksums to send.  This is called by both
 * the generator and the sender. */
void write_sum_head(int f, struct sum_struct *sum)
{
	static struct sum_struct null_sum;

	if (sum == NULL)
		sum = &null_sum;

	write_int(f, sum->count);
	write_int(f, sum->blength);
	if (protocol_version >= 27)
		write_int(f, sum->s2length);
	write_int(f, sum->remainder);
}

/* Sleep after writing to limit I/O bandwidth usage.
 *
 * @todo Rather than sleeping after each write, it might be better to
 * use some kind of averaging.  The current algorithm seems to always
 * use a bit less bandwidth than specified, because it doesn't make up
 * for slow periods.  But arguably this is a feature.  In addition, we
 * ought to take the time used to write the data into account.
 *
 * During some phases of big transfers (file FOO is uptodate) this is
 * called with a small bytes_written every time.  As the kernel has to
 * round small waits up to guarantee that we actually wait at least the
 * requested number of microseconds, this can become grossly inaccurate.
 * We therefore keep track of the bytes we've written over time and only
 * sleep when the accumulated delay is at least 1 tenth of a second. */
static void sleep_for_bwlimit(int bytes_written)
{
	static struct timeval prior_tv;
	static long total_written = 0;
	struct timeval tv, start_tv;
	long elapsed_usec, sleep_usec;

#define ONE_SEC	1000000L /* # of microseconds in a second */

	total_written += bytes_written;

	gettimeofday(&start_tv, NULL);
	if (prior_tv.tv_sec) {
		elapsed_usec = (start_tv.tv_sec - prior_tv.tv_sec) * ONE_SEC
			     + (start_tv.tv_usec - prior_tv.tv_usec);
		total_written -= (int64)elapsed_usec * bwlimit / (ONE_SEC/1024);
		if (total_written < 0)
			total_written = 0;
	}

	sleep_usec = total_written * (ONE_SEC/1024) / bwlimit;
	if (sleep_usec < ONE_SEC / 10) {
		prior_tv = start_tv;
		return;
	}

	tv.tv_sec  = sleep_usec / ONE_SEC;
	tv.tv_usec = sleep_usec % ONE_SEC;
	select(0, NULL, NULL, NULL, &tv);

	gettimeofday(&prior_tv, NULL);
	elapsed_usec = (prior_tv.tv_sec - start_tv.tv_sec) * ONE_SEC
		     + (prior_tv.tv_usec - start_tv.tv_usec);
	total_written = (sleep_usec - elapsed_usec) * bwlimit / (ONE_SEC/1024);
}

void io_flush(int flush_type)
{
	if (iobuf.out.len > iobuf.out_empty_len) {
		if (flush_type == FULL_FLUSH)		/* flush everything in the output buffers */
			perform_io(iobuf.out.size - iobuf.out_empty_len, PIO_NEED_OUTROOM);
		else if (flush_type == NORMAL_FLUSH)	/* flush at least 1 byte */
			perform_io(iobuf.out.size - iobuf.out.len + 1, PIO_NEED_OUTROOM);
							/* MSG_FLUSH: flush iobuf.msg only */
	}
	if (iobuf.msg.len)
		perform_io(iobuf.msg.size, PIO_NEED_MSGROOM);
}

void write_shortint(int f, unsigned short x)
{
	char b[2];
	b[0] = (char)x;
	b[1] = (char)(x >> 8);
	write_buf(f, b, 2);
}

void write_int(int f, int32 x)
{
	char b[4];
	SIVAL(b, 0, x);
	write_buf(f, b, 4);
}

void write_varint(int f, int32 x)
{
	char b[5];
	uchar bit;
	int cnt;

	SIVAL(b, 1, x);

	for (cnt = 4; cnt > 1 && b[cnt] == 0; cnt--) {}
	bit = ((uchar)1<<(7-cnt+1));

	if (CVAL(b, cnt) >= bit) {
		cnt++;
		*b = ~(bit-1);
	} else if (cnt > 1)
		*b = b[cnt] | ~(bit*2-1);
	else
		*b = b[1];

	write_buf(f, b, cnt);
}

void write_varlong(int f, int64 x, uchar min_bytes)
{
	char b[9];
	uchar bit;
	int cnt = 8;

#if SIZEOF_INT64 >= 8
	SIVAL64(b, 1, x);
#else
	SIVAL(b, 1, x);
	if (x <= 0x7FFFFFFF && x >= 0)
		memset(b + 5, 0, 4);
	else {
		rprintf(FERROR, "Integer overflow: attempted 64-bit offset\n");
		exit_cleanup(RERR_UNSUPPORTED);
	}
#endif

	while (cnt > min_bytes && b[cnt] == 0)
		cnt--;
	bit = ((uchar)1<<(7-cnt+min_bytes));
	if (CVAL(b, cnt) >= bit) {
		cnt++;
		*b = ~(bit-1);
	} else if (cnt > min_bytes)
		*b = b[cnt] | ~(bit*2-1);
	else
		*b = b[cnt];

	write_buf(f, b, cnt);
}

/*
 * Note: int64 may actually be a 32-bit type if ./configure couldn't find any
 * 64-bit types on this platform.
 */
void write_longint(int f, int64 x)
{
	char b[12], * const s = b+4;

	SIVAL(s, 0, x);
	if (x <= 0x7FFFFFFF && x >= 0) {
		write_buf(f, s, 4);
		return;
	}

#if SIZEOF_INT64 < 8
	rprintf(FERROR, "Integer overflow: attempted 64-bit offset\n");
	exit_cleanup(RERR_UNSUPPORTED);
#else
	memset(b, 0xFF, 4);
	SIVAL(s, 4, x >> 32);
	write_buf(f, b, 12);
#endif
}

void write_bigbuf(int f, const char *buf, size_t len)
{
	size_t half_max = (iobuf.out.size - iobuf.out_empty_len) / 2;

	while (len > half_max + 1024) {
		write_buf(f, buf, half_max);
		buf += half_max;
		len -= half_max;
	}

	write_buf(f, buf, len);
}

void write_buf(int f, const char *buf, size_t len)
{
	size_t pos, siz;

	if (f != iobuf.out_fd) {
		safe_write(f, buf, len);
		goto batch_copy;
	}

	if (iobuf.out.len + len > iobuf.out.size)
		perform_io(len, PIO_NEED_OUTROOM);

	pos = iobuf.out.pos + iobuf.out.len; /* Must be set after any flushing. */
	if (pos >= iobuf.out.size)
		pos -= iobuf.out.size;

	/* Handle a split copy if we wrap around the end of the circular buffer. */
	if (pos >= iobuf.out.pos && (siz = iobuf.out.size - pos) < len) {
		memcpy(iobuf.out.buf + pos, buf, siz);
		memcpy(iobuf.out.buf, buf + siz, len - siz);
	} else
		memcpy(iobuf.out.buf + pos, buf, len);

	iobuf.out.len += len;
	total_data_written += len;

  batch_copy:
	if (f == write_batch_monitor_out)
		safe_write(batch_fd, buf, len);
}

/* Write a string to the connection */
void write_sbuf(int f, const char *buf)
{
	write_buf(f, buf, strlen(buf));
}

void write_byte(int f, uchar c)
{
	write_buf(f, (char *)&c, 1);
}

void write_vstring(int f, const char *str, int len)
{
	uchar lenbuf[3], *lb = lenbuf;

	if (len > 0x7F) {
		if (len > 0x7FFF) {
			rprintf(FERROR,
				"attempting to send over-long vstring (%d > %d)\n",
				len, 0x7FFF);
			exit_cleanup(RERR_PROTOCOL);
		}
		*lb++ = len / 0x100 + 0x80;
	}
	*lb = len;

	write_buf(f, (char*)lenbuf, lb - lenbuf + 1);
	if (len)
		write_buf(f, str, len);
}

/* Send a file-list index using a byte-reduction method. */
void write_ndx(int f, int32 ndx)
{
	static int32 prev_positive = -1, prev_negative = 1;
	int32 diff, cnt = 0;
	char b[6];

	if (protocol_version < 30 || read_batch) {
		write_int(f, ndx);
		return;
	}

	/* Send NDX_DONE as a single-byte 0 with no side effects.  Send
	 * negative nums as a positive after sending a leading 0xFF. */
	if (ndx >= 0) {
		diff = ndx - prev_positive;
		prev_positive = ndx;
	} else if (ndx == NDX_DONE) {
		*b = 0;
		write_buf(f, b, 1);
		return;
	} else {
		b[cnt++] = (char)0xFF;
		ndx = -ndx;
		diff = ndx - prev_negative;
		prev_negative = ndx;
	}

	/* A diff of 1 - 253 is sent as a one-byte diff; a diff of 254 - 32767
	 * or 0 is sent as a 0xFE + a two-byte diff; otherwise we send 0xFE
	 * & all 4 bytes of the (non-negative) num with the high-bit set. */
	if (diff < 0xFE && diff > 0)
		b[cnt++] = (char)diff;
	else if (diff < 0 || diff > 0x7FFF) {
		b[cnt++] = (char)0xFE;
		b[cnt++] = (char)((ndx >> 24) | 0x80);
		b[cnt++] = (char)ndx;
		b[cnt++] = (char)(ndx >> 8);
		b[cnt++] = (char)(ndx >> 16);
	} else {
		b[cnt++] = (char)0xFE;
		b[cnt++] = (char)(diff >> 8);
		b[cnt++] = (char)diff;
	}
	write_buf(f, b, cnt);
}

/* Receive a file-list index using a byte-reduction method. */
int32 read_ndx(int f)
{
	static int32 prev_positive = -1, prev_negative = 1;
	int32 *prev_ptr, num;
	char b[4];

	if (protocol_version < 30)
		return read_int(f);

	read_buf(f, b, 1);
	if (CVAL(b, 0) == 0xFF) {
		read_buf(f, b, 1);
		prev_ptr = &prev_negative;
	} else if (CVAL(b, 0) == 0)
		return NDX_DONE;
	else
		prev_ptr = &prev_positive;
	if (CVAL(b, 0) == 0xFE) {
		read_buf(f, b, 2);
		if (CVAL(b, 0) & 0x80) {
			b[3] = CVAL(b, 0) & ~0x80;
			b[0] = b[1];
			read_buf(f, b+1, 2);
			num = IVAL(b, 0);
		} else
			num = (UVAL(b,0)<<8) + UVAL(b,1) + *prev_ptr;
	} else
		num = UVAL(b, 0) + *prev_ptr;
	*prev_ptr = num;
	if (prev_ptr == &prev_negative)
		num = -num;
	return num;
}

/* Read a line of up to bufsiz-1 characters into buf.  Strips
 * the (required) trailing newline and all carriage returns.
 * Returns 1 for success; 0 for I/O error or truncation. */
int read_line_old(int fd, char *buf, size_t bufsiz, int eof_ok)
{
	assert(fd != iobuf.in_fd);
	bufsiz--; /* leave room for the null */
	while (bufsiz > 0) {
		if (safe_read(fd, buf, 1) == 0) {
			if (eof_ok)
				break;
			return 0;
		}
		if (*buf == '\0')
			return 0;
		if (*buf == '\n')
			break;
		if (*buf != '\r') {
			buf++;
			bufsiz--;
		}
	}
	*buf = '\0';
	return bufsiz > 0;
}

void io_printf(int fd, const char *format, ...)
{
	va_list ap;
	char buf[BIGPATHBUFLEN];
	int len;

	va_start(ap, format);
	len = vsnprintf(buf, sizeof buf, format, ap);
	va_end(ap);

	if (len < 0)
		exit_cleanup(RERR_PROTOCOL);

	if (len >= (int)sizeof buf) {
		rprintf(FERROR, "io_printf() was too long for the buffer.\n");
		exit_cleanup(RERR_PROTOCOL);
	}

	write_sbuf(fd, buf);
}

/* Setup for multiplexing a MSG_* stream with the data stream. */
void io_start_multiplex_out(int fd)
{
	io_flush(FULL_FLUSH);

	if (msgs2stderr == 1 && DEBUG_GTE(IO, 2))
		rprintf(FINFO, "[%s] io_start_multiplex_out(%d)\n", who_am_i(), fd);

	if (!iobuf.msg.buf)
		alloc_xbuf(&iobuf.msg, ROUND_UP_1024(IO_BUFFER_SIZE));

	iobuf.out_empty_len = 4; /* See also OUT_MULTIPLEXED */
	io_start_buffering_out(fd);
	got_kill_signal = 0;

	iobuf.raw_data_header_pos = iobuf.out.pos + iobuf.out.len;
	iobuf.out.len += 4;
}

/* Setup for multiplexing a MSG_* stream with the data stream. */
void io_start_multiplex_in(int fd)
{
	if (msgs2stderr == 1 && DEBUG_GTE(IO, 2))
		rprintf(FINFO, "[%s] io_start_multiplex_in(%d)\n", who_am_i(), fd);

	iobuf.in_multiplexed = 1; /* See also IN_MULTIPLEXED */
	io_start_buffering_in(fd);
}

int io_end_multiplex_in(int mode)
{
	int ret = iobuf.in_multiplexed ? iobuf.in_fd : -1;

	if (msgs2stderr == 1 && DEBUG_GTE(IO, 2))
		rprintf(FINFO, "[%s] io_end_multiplex_in(mode=%d)\n", who_am_i(), mode);

	iobuf.in_multiplexed = 0;
	if (mode == MPLX_SWITCHING)
		iobuf.raw_input_ends_before = 0;
	else
		assert(iobuf.raw_input_ends_before == 0);
	if (mode != MPLX_TO_BUFFERED)
		io_end_buffering_in(mode);

	return ret;
}

int io_end_multiplex_out(int mode)
{
	int ret = iobuf.out_empty_len ? iobuf.out_fd : -1;

	if (msgs2stderr == 1 && DEBUG_GTE(IO, 2))
		rprintf(FINFO, "[%s] io_end_multiplex_out(mode=%d)\n", who_am_i(), mode);

	if (mode != MPLX_TO_BUFFERED)
		io_end_buffering_out(mode);
	else
		io_flush(FULL_FLUSH);

	iobuf.out.len = 0;
	iobuf.out_empty_len = 0;
	if (got_kill_signal > 0) /* Just in case... */
		handle_kill_signal(False);
	got_kill_signal = -1;

	return ret;
}

void start_write_batch(int fd)
{
	/* Some communication has already taken place, but we don't
	 * enable batch writing until here so that we can write a
	 * canonical record of the communication even though the
	 * actual communication so far depends on whether a daemon
	 * is involved. */
	write_int(batch_fd, protocol_version);
	if (protocol_version >= 30)
		write_varint(batch_fd, compat_flags);
	write_int(batch_fd, checksum_seed);

	if (am_sender)
		write_batch_monitor_out = fd;
	else
		write_batch_monitor_in = fd;
}

void stop_write_batch(void)
{
	write_batch_monitor_out = -1;
	write_batch_monitor_in = -1;
}
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 *
 * This is based on loadparm.c from Samba, written by Andrew Tridgell
 * and Karl Auer.  Some of the changes are:
 *
 * Copyright (C) 2001, 2002 Martin Pool <mbp@samba.org>
 * Copyright (C) 2003-2020 Wayne Davison
 */

/* Load parameters.
 *
 *  This module provides suitable callback functions for the params
 *  module. It builds the internal table of section details which is
 *  then used by the rest of the server.
 *
 * To add a parameter:
 *
 * 1) add it to the global_vars or local_vars structure definition
 * 2) add it to the parm_table
 * 3) add it to the list of available functions (eg: using FN_GLOBAL_STRING())
 * 4) initialise it in the Defaults static structure
 *
 * Notes:
 *   The configuration file is processed sequentially for speed. For this
 *   reason, there is a fair bit of sequence-dependent code here - ie., code
 *   which assumes that certain things happen before others. In particular, the
 *   code which happens at the boundary between sections is delicately poised,
 *   so be careful!
 */

#include "rsync.h"
#include "itypes.h"
#include "ifuncs.h"
#include "default-dont-compress.h"

extern item_list dparam_list;

#define strequal(a, b) (strcasecmp(a, b)==0)

#ifndef LOG_DAEMON
#define LOG_DAEMON 0
#endif

/* the following are used by loadparm for option lists */
typedef enum {
	P_BOOL, P_BOOLREV, P_BOOL3, P_CHAR, P_INTEGER,
	P_OCTAL, P_PATH, P_STRING, P_ENUM
} parm_type;

typedef enum {
	P_LOCAL, P_GLOBAL, P_NONE
} parm_class;

struct enum_list {
	int value;
	char *name;
};

struct parm_struct {
	char *label;
	parm_type type;
	parm_class class;
	void *ptr;
	struct enum_list *enum_list;
	unsigned flags;
};

#ifndef GLOBAL_NAME
#define GLOBAL_NAME "global"
#endif

/* some helpful bits */
#define iSECTION(i) ((local_vars*)section_list.items)[i]
#define LP_SNUM_OK(i) ((i) >= 0 && (i) < (int)section_list.count)
#define SECTION_PTR(s, p) (((char*)(s)) + (ptrdiff_t)(((char*)(p))-(char*)&Vars.l))

/* Stack of "Vars" values used by the &include directive. */
static item_list Vars_stack = EMPTY_ITEM_LIST;

/* The array of section values that holds all the defined modules. */
static item_list section_list = EMPTY_ITEM_LIST;

static int iSectionIndex = -1;
static BOOL bInGlobalSection = True;

static struct enum_list enum_syslog_facility[] = {
#ifdef LOG_AUTH
	{ LOG_AUTH, "auth" },
#endif
#ifdef LOG_AUTHPRIV
	{ LOG_AUTHPRIV, "authpriv" },
#endif
#ifdef LOG_CRON
	{ LOG_CRON, "cron" },
#endif
#ifdef LOG_DAEMON
	{ LOG_DAEMON, "daemon" },
#endif
#ifdef LOG_FTP
	{ LOG_FTP, "ftp" },
#endif
#ifdef LOG_KERN
	{ LOG_KERN, "kern" },
#endif
#ifdef LOG_LPR
	{ LOG_LPR, "lpr" },
#endif
#ifdef LOG_MAIL
	{ LOG_MAIL, "mail" },
#endif
#ifdef LOG_NEWS
	{ LOG_NEWS, "news" },
#endif
#ifdef LOG_AUTH
	{ LOG_AUTH, "security" },
#endif
#ifdef LOG_SYSLOG
	{ LOG_SYSLOG, "syslog" },
#endif
#ifdef LOG_USER
	{ LOG_USER, "user" },
#endif
#ifdef LOG_UUCP
	{ LOG_UUCP, "uucp" },
#endif
#ifdef LOG_LOCAL0
	{ LOG_LOCAL0, "local0" },
#endif
#ifdef LOG_LOCAL1
	{ LOG_LOCAL1, "local1" },
#endif
#ifdef LOG_LOCAL2
	{ LOG_LOCAL2, "local2" },
#endif
#ifdef LOG_LOCAL3
	{ LOG_LOCAL3, "local3" },
#endif
#ifdef LOG_LOCAL4
	{ LOG_LOCAL4, "local4" },
#endif
#ifdef LOG_LOCAL5
	{ LOG_LOCAL5, "local5" },
#endif
#ifdef LOG_LOCAL6
	{ LOG_LOCAL6, "local6" },
#endif
#ifdef LOG_LOCAL7
	{ LOG_LOCAL7, "local7" },
#endif
	{ -1, NULL }
};

/* Expand %VAR% references.  Any unknown vars or unrecognized
 * syntax leaves the raw chars unchanged. */
static char *expand_vars(const char *str)
{
	char *buf, *t;
	const char *f;
	int bufsize;

	if (!str || !strchr(str, '%'))
		return (char *)str; /* TODO change return value to const char* at some point. */

	bufsize = strlen(str) + 2048;
	buf = new_array(char, bufsize+1); /* +1 for trailing '\0' */

	for (t = buf, f = str; bufsize && *f; ) {
		if (*f == '%' && isUpper(f+1)) {
			char *percent = strchr(f+1, '%');
			if (percent && percent - f < bufsize) {
				char *val;
				strlcpy(t, f+1, percent - f);
				val = getenv(t);
				if (val) {
					int len = strlcpy(t, val, bufsize+1);
					if (len > bufsize)
						break;
					bufsize -= len;
					t += len;
					f = percent + 1;
					continue;
				}
			}
		}
		*t++ = *f++;
		bufsize--;
	}
	*t = '\0';

	if (*f) {
		rprintf(FLOG, "Overflowed buf in expand_vars() trying to expand: %s\n", str);
		exit_cleanup(RERR_MALLOC);
	}

	if (bufsize && (buf = realloc(buf, t - buf + 1)) == NULL)
		out_of_memory("expand_vars");

	return buf;
}

/* Each "char* foo" has an associated "BOOL foo_EXP" that tracks if the string has been expanded yet or not. */

/* NOTE: use this function and all the FN_{GLOBAL,LOCAL} ones WITHOUT a trailing semicolon! */
#define RETURN_EXPANDED(val) {if (!val ## _EXP) {val = expand_vars(val); val ## _EXP = True;} return val ? val : "";}

/* In this section all the functions that are used to access the
 * parameters from the rest of the program are defined. */

#define FN_GLOBAL_STRING(fn_name, val) \
 char *fn_name(void) RETURN_EXPANDED(Vars.g.val)
#define FN_GLOBAL_BOOL(fn_name, val) \
 BOOL fn_name(void) {return Vars.g.val;}
#define FN_GLOBAL_CHAR(fn_name, val) \
 char fn_name(void) {return Vars.g.val;}
#define FN_GLOBAL_INTEGER(fn_name, val) \
 int fn_name(void) {return Vars.g.val;}

#define FN_LOCAL_STRING(fn_name, val) \
 char *fn_name(int i) {if (LP_SNUM_OK(i) && iSECTION(i).val) RETURN_EXPANDED(iSECTION(i).val) else RETURN_EXPANDED(Vars.l.val)}
#define FN_LOCAL_BOOL(fn_name, val) \
 BOOL fn_name(int i) {return LP_SNUM_OK(i)? iSECTION(i).val : Vars.l.val;}
#define FN_LOCAL_CHAR(fn_name, val) \
 char fn_name(int i) {return LP_SNUM_OK(i)? iSECTION(i).val : Vars.l.val;}
#define FN_LOCAL_INTEGER(fn_name, val) \
 int fn_name(int i) {return LP_SNUM_OK(i)? iSECTION(i).val : Vars.l.val;}

/* The following include file contains:
 *
 * typedef global_vars - describes global (ie., server-wide) parameters.
 * typedef local_vars - describes a single section.
 * typedef all_vars - a combination of global_vars & local_vars.
 * all_vars Defaults - the default values for all the variables.
 * all_vars Vars - the currently configured values for all the variables.
 * struct parm_struct parm_table - the strings & variables for the parser.
 * FN_{LOCAL,GLOBAL}_{TYPE}() definition for all the lp_var_name() accessors.
 */

#include "daemon-parm.h"

/* Initialise the Default all_vars structure. */
void reset_daemon_vars(void)
{
	memcpy(&Vars, &Defaults, sizeof Vars);
}

/* Assign a copy of v to *s.  Handles NULL strings.  We don't worry
 * about overwriting a malloc'd string because the long-running
 * (port-listening) daemon only loads the config file once, and the
 * per-job (forked or xinitd-ran) daemon only re-reads the file at
 * the start, so any lost memory is inconsequential. */
static inline void string_set(char **s, const char *v)
{
	*s = v ? strdup(v) : NULL;
}

/* Copy local_vars into a new section. No need to strdup since we don't free. */
static void copy_section(local_vars *psectionDest, local_vars *psectionSource)
{
	memcpy(psectionDest, psectionSource, sizeof psectionDest[0]);
}

/* Initialise a section to the defaults. */
static void init_section(local_vars *psection)
{
	memset(psection, 0, sizeof (local_vars));
	copy_section(psection, &Vars.l);
}

/* Do a case-insensitive, whitespace-ignoring string equality check. */
static int strwiEQ(char *psz1, char *psz2)
{
	/* If one or both strings are NULL, we return equality right away. */
	if (psz1 == psz2)
		return 1;
	if (psz1 == NULL || psz2 == NULL)
		return 0;

	/* sync the strings on first non-whitespace */
	while (1) {
		while (isSpace(psz1))
			psz1++;
		while (isSpace(psz2))
			psz2++;
		if (*psz1 == '\0' || *psz2 == '\0')
			break;
		if (toUpper(psz1) != toUpper(psz2))
			break;
		psz1++;
		psz2++;
	}
	return *psz1 == *psz2;
}

/* Find a section by name. Otherwise works like get_section. */
static int getsectionbyname(char *name)
{
	int i;

	for (i = section_list.count - 1; i >= 0; i--) {
		if (strwiEQ(iSECTION(i).name, name))
			break;
	}

	return i;
}

/* Add a new section to the sections array w/the default values. */
static int add_a_section(char *name)
{
	int i;
	local_vars *s;

	/* it might already exist */
	if (name) {
		i = getsectionbyname(name);
		if (i >= 0)
			return i;
	}

	i = section_list.count;
	s = EXPAND_ITEM_LIST(&section_list, local_vars, 2);

	init_section(s);
	if (name)
		string_set(&s->name, name);

	return i;
}

/* Map a parameter's string representation to something we can use.
 * Returns False if the parameter string is not recognised, else TRUE. */
static int map_parameter(char *parmname)
{
	int iIndex;

	if (*parmname == '-')
		return -1;

	for (iIndex = 0; parm_table[iIndex].label; iIndex++) {
		if (strwiEQ(parm_table[iIndex].label, parmname))
			return iIndex;
	}

	rprintf(FLOG, "Unknown Parameter encountered: \"%s\"\n", parmname);
	return -1;
}

/* Set a boolean variable from the text value stored in the passed string.
 * Returns True in success, False if the passed string does not correctly
 * represent a boolean. */
static BOOL set_boolean(BOOL *pb, char *parmvalue, int allow_unset)
{
	if (strwiEQ(parmvalue, "yes") || strwiEQ(parmvalue, "true") || strwiEQ(parmvalue, "1"))
		*pb = True;
	else if (strwiEQ(parmvalue, "no") || strwiEQ(parmvalue, "false") || strwiEQ(parmvalue, "0"))
		*pb = False;
	else if (allow_unset && (strwiEQ(parmvalue, "unset") || strwiEQ(parmvalue, "-1")))
		*pb = Unset;
	else {
		rprintf(FLOG, "Badly formed boolean in configuration file: \"%s\".\n", parmvalue);
		return False;
	}
	return True;
}

/* Process a parameter. */
static BOOL do_parameter(char *parmname, char *parmvalue)
{
	int parmnum, i;
	void *parm_ptr; /* where we are going to store the result */
	void *def_ptr;
	char *cp;

	parmnum = map_parameter(parmname);

	if (parmnum < 0) {
		rprintf(FLOG, "IGNORING unknown parameter \"%s\"\n", parmname);
		return True;
	}

	def_ptr = parm_table[parmnum].ptr;

	if (bInGlobalSection)
		parm_ptr = def_ptr;
	else {
		if (parm_table[parmnum].class == P_GLOBAL) {
			rprintf(FLOG, "Global parameter %s found in module section!\n", parmname);
			return True;
		}
		parm_ptr = SECTION_PTR(&iSECTION(iSectionIndex), def_ptr);
	}

	/* now switch on the type of variable it is */
	switch (parm_table[parmnum].type) {
	case P_PATH:
	case P_STRING:
		/* delay expansion of %VAR% strings */
		break;
	default:
		/* expand any %VAR% strings now */
		parmvalue = expand_vars(parmvalue);
		break;
	}

	switch (parm_table[parmnum].type) {
	case P_BOOL:
		set_boolean(parm_ptr, parmvalue, False);
		break;

	case P_BOOL3:
		set_boolean(parm_ptr, parmvalue, True);
		break;

	case P_BOOLREV:
		set_boolean(parm_ptr, parmvalue, False);
		*(BOOL *)parm_ptr = ! *(BOOL *)parm_ptr;
		break;

	case P_INTEGER:
		*(int *)parm_ptr = atoi(parmvalue);
		break;

	case P_CHAR:
		*(char *)parm_ptr = *parmvalue;
		break;

	case P_OCTAL:
		sscanf(parmvalue, "%o", (unsigned int *)parm_ptr);
		break;

	case P_PATH:
		string_set(parm_ptr, parmvalue);
		if ((cp = *(char**)parm_ptr) != NULL) {
			int len = strlen(cp);
			while (len > 1 && cp[len-1] == '/') len--;
			cp[len] = '\0';
		}
		break;

	case P_STRING:
		string_set(parm_ptr, parmvalue);
		break;

	case P_ENUM:
		for (i=0; parm_table[parmnum].enum_list[i].name; i++) {
			if (strequal(parmvalue, parm_table[parmnum].enum_list[i].name)) {
				*(int *)parm_ptr = parm_table[parmnum].enum_list[i].value;
				break;
			}
		}
		if (!parm_table[parmnum].enum_list[i].name) {
			if (atoi(parmvalue) > 0)
				*(int *)parm_ptr = atoi(parmvalue);
		}
		break;
	}

	return True;
}

/* Process a new section (rsync module).
 * Returns True on success, False on failure. */
static BOOL do_section(char *sectionname)
{
	BOOL isglobal;

	if (*sectionname == ']') { /* A special push/pop/reset directive from params.c */
		bInGlobalSection = 1;
		if (strcmp(sectionname+1, "push") == 0) {
			all_vars *vp = EXPAND_ITEM_LIST(&Vars_stack, all_vars, 2);
			memcpy(vp, &Vars, sizeof Vars);
		} else if (strcmp(sectionname+1, "pop") == 0
		 || strcmp(sectionname+1, "reset") == 0) {
			all_vars *vp = ((all_vars*)Vars_stack.items) + Vars_stack.count - 1;
			if (!Vars_stack.count)
				return False;
			memcpy(&Vars, vp, sizeof Vars);
			if (sectionname[1] == 'p')
				Vars_stack.count--;
		} else
			return False;
		return True;
	}

	isglobal = strwiEQ(sectionname, GLOBAL_NAME);

	/* At the end of the global section, add any --dparam items. */
	if (bInGlobalSection && !isglobal) {
		if (!section_list.count)
			set_dparams(0);
	}

	/* if we've just struck a global section, note the fact. */
	bInGlobalSection = isglobal;

	/* check for multiple global sections */
	if (bInGlobalSection)
		return True;

#if 0
	/* If we have a current section, tidy it up before moving on. */
	if (iSectionIndex >= 0) {
		/* Add any tidy work as needed ... */
		if (problem)
			return False;
	}
#endif

	if (strchr(sectionname, '/') != NULL) {
		rprintf(FLOG, "Warning: invalid section name in configuration file: %s\n", sectionname);
		return False;
	}

	if ((iSectionIndex = add_a_section(sectionname)) < 0) {
		rprintf(FLOG, "Failed to add a new module\n");
		bInGlobalSection = True;
		return False;
	}

	return True;
}

/* Load the modules from the config file. Return True on success,
 * False on failure. */
int lp_load(char *pszFname, int globals_only)
{
	bInGlobalSection = True;

	reset_daemon_vars();

	/* We get sections first, so have to start 'behind' to make up. */
	iSectionIndex = -1;
	return pm_process(pszFname, globals_only ? NULL : do_section, do_parameter);
}

BOOL set_dparams(int syntax_check_only)
{
	char *equal, *val, **params = dparam_list.items;
	unsigned j;

	for (j = 0; j < dparam_list.count; j++) {
		equal = strchr(params[j], '='); /* options.c verified this */
		*equal = '\0';
		if (syntax_check_only) {
			if (map_parameter(params[j]) < 0) {
				rprintf(FERROR, "Unknown parameter \"%s\"\n", params[j]);
				*equal = '=';
				return False;
			}
		} else {
			for (val = equal+1; isSpace(val); val++) {}
			do_parameter(params[j], val);
		}
		*equal = '=';
	}

	return True;
}

/* Return the max number of modules (sections). */
int lp_num_modules(void)
{
	return section_list.count;
}

/* Return the number of the module with the given name, or -1 if it doesn't
 * exist. Note that this is a DIFFERENT ANIMAL from the internal function
 * getsectionbyname()! This works ONLY if all sections have been loaded,
 * and does not copy the found section. */
int lp_number(char *name)
{
	int i;

	for (i = section_list.count - 1; i >= 0; i--) {
		if (strcmp(lp_name(i), name) == 0)
			break;
	}

	return i;
}
/*
 * Logging and utility functions.
 *
 * Copyright (C) 1998-2001 Andrew Tridgell <tridge@samba.org>
 * Copyright (C) 2000-2001 Martin Pool <mbp@samba.org>
 * Copyright (C) 2003-2022 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"
#include "itypes.h"
#include "inums.h"

extern int dry_run;
extern int am_daemon;
extern int am_server;
extern int am_sender;
extern int am_generator;
extern int local_server;
extern int quiet;
extern int module_id;
extern int allow_8bit_chars;
extern int protocol_version;
extern int always_checksum;
extern int preserve_mtimes;
extern int msgs2stderr;
extern int stdout_format_has_i;
extern int stdout_format_has_o_or_i;
extern int logfile_format_has_i;
extern int logfile_format_has_o_or_i;
extern int receiver_symlink_times;
extern int64 total_data_written;
extern int64 total_data_read;
extern mode_t orig_umask;
extern char *auth_user;
extern char *stdout_format;
extern char *logfile_format;
extern char *logfile_name;
#ifdef ICONV_CONST
extern iconv_t ic_chck;
#endif
#ifdef ICONV_OPTION
extern iconv_t ic_recv;
#endif
extern char curr_dir[MAXPATHLEN];
extern char *full_module_path;
extern unsigned int module_dirlen;
extern char sender_file_sum[MAX_DIGEST_LEN];
extern const char undetermined_hostname[];

extern struct name_num_item *xfer_sum_nni, *file_sum_nni;

static int log_initialised;
static int logfile_was_closed;
static FILE *logfile_fp;
struct stats stats;

int got_xfer_error = 0;
int output_needs_newline = 0;
int send_msgs_to_gen = 0;

static int64 initial_data_written;
static int64 initial_data_read;

struct {
	int code;
	char const *name;
} const rerr_names[] = {
	{ RERR_SYNTAX     , "syntax or usage error" },
	{ RERR_PROTOCOL   , "protocol incompatibility" },
	{ RERR_FILESELECT , "errors selecting input/output files, dirs" },
	{ RERR_UNSUPPORTED, "requested action not supported" },
	{ RERR_STARTCLIENT, "error starting client-server protocol" },
	{ RERR_SOCKETIO   , "error in socket IO" },
	{ RERR_FILEIO     , "error in file IO" },
	{ RERR_STREAMIO   , "error in rsync protocol data stream" },
	{ RERR_MESSAGEIO  , "errors with program diagnostics" },
	{ RERR_IPC        , "error in IPC code" },
	{ RERR_CRASHED    , "sibling process crashed" },
	{ RERR_TERMINATED , "sibling process terminated abnormally" },
	{ RERR_SIGNAL1    , "received SIGUSR1" },
	{ RERR_SIGNAL     , "received SIGINT, SIGTERM, or SIGHUP" },
	{ RERR_WAITCHILD  , "waitpid() failed" },
	{ RERR_MALLOC     , "error allocating core memory buffers" },
	{ RERR_PARTIAL    , "some files/attrs were not transferred (see previous errors)" },
	{ RERR_VANISHED   , "some files vanished before they could be transferred" },
	{ RERR_DEL_LIMIT  , "the --max-delete limit stopped deletions" },
	{ RERR_TIMEOUT    , "timeout in data send/receive" },
	{ RERR_CONTIMEOUT , "timeout waiting for daemon connection" },
	{ RERR_CMD_FAILED , "remote shell failed" },
	{ RERR_CMD_KILLED , "remote shell killed" },
	{ RERR_CMD_RUN    , "remote command could not be run" },
	{ RERR_CMD_NOTFOUND,"remote command not found" },
	{ 0, NULL }
};

/*
 * Map from rsync error code to name, or return NULL.
 */
static char const *rerr_name(int code)
{
	int i;
	for (i = 0; rerr_names[i].name; i++) {
		if (rerr_names[i].code == code)
			return rerr_names[i].name;
	}
	return NULL;
}

static void logit(int priority, const char *buf)
{
	if (logfile_was_closed)
		logfile_reopen();
	if (logfile_fp) {
		fprintf(logfile_fp, "%s [%d] %s", timestring(time(NULL)), (int)getpid(), buf);
		fflush(logfile_fp);
	} else {
		syslog(priority, "%s", buf);
	}
}

static void syslog_init(void)
{
	int options = LOG_PID;

#ifdef LOG_NDELAY
	options |= LOG_NDELAY;
#endif

#ifdef LOG_DAEMON
	openlog(lp_syslog_tag(module_id), options, lp_syslog_facility(module_id));
#else
	openlog(lp_syslog_tag(module_id), options);
#endif

#ifndef LOG_NDELAY
	logit(LOG_INFO, "rsyncd started\n");
#endif
}

static void logfile_open(void)
{
	mode_t old_umask = umask(022 | orig_umask);
	logfile_fp = fopen(logfile_name, "a");
	umask(old_umask);
	if (!logfile_fp) {
		int fopen_errno = errno;
		/* Rsync falls back to using syslog on failure. */
		syslog_init();
		rsyserr(FERROR, fopen_errno,
			"failed to open log-file %s", logfile_name);
		rprintf(FINFO, "Ignoring \"log file\" setting.\n");
		logfile_name = "";
	}
}

void log_init(int restart)
{
	if (log_initialised) {
		if (!restart) /* Note: a restart only happens with am_daemon */
			return;
		assert(logfile_name); /* all am_daemon procs got at least an empty string */
		if (strcmp(logfile_name, lp_log_file(module_id)) != 0) {
			if (logfile_fp) {
				fclose(logfile_fp);
				logfile_fp = NULL;
			} else
				closelog();
			logfile_name = NULL;
		} else if (*logfile_name)
			return; /* unchanged, non-empty "log file" names */
		else if (lp_syslog_facility(-1) != lp_syslog_facility(module_id)
		      || strcmp(lp_syslog_tag(-1), lp_syslog_tag(module_id)) != 0)
			closelog();
		else
			return; /* unchanged syslog settings */
	} else
		log_initialised = 1;

	/* This looks pointless, but it is needed in order for the
	 * C library on some systems to fetch the timezone info
	 * before the chroot. */
	timestring(time(NULL));

	/* Optionally use a log file instead of syslog.  (Non-daemon
	 * rsyncs will have already set logfile_name, as needed.) */
	if (am_daemon && !logfile_name)
		logfile_name = lp_log_file(module_id);
	if (logfile_name && *logfile_name)
		logfile_open();
	else
		syslog_init();
}

/* Note that this close & reopen idiom intentionally ignores syslog logging. */
void logfile_close(void)
{
	if (logfile_fp) {
		logfile_was_closed = 1;
		fclose(logfile_fp);
		logfile_fp = NULL;
	}
}

void logfile_reopen(void)
{
	if (logfile_was_closed) {
		logfile_was_closed = 0;
		logfile_open();
	}
}

static void filtered_fwrite(FILE *f, const char *in_buf, int in_len, int use_isprint, char end_char)
{
	char outbuf[1024], *ob = outbuf;
	const char *end = in_buf + in_len;
	while (in_buf < end) {
		if (ob - outbuf >= (int)sizeof outbuf - 10) {
			if (fwrite(outbuf, ob - outbuf, 1, f) != 1)
				exit_cleanup(RERR_MESSAGEIO);
			ob = outbuf;
		}
		if ((in_buf < end - 4 && *in_buf == '\\' && in_buf[1] == '#'
		  && isDigit(in_buf + 2) && isDigit(in_buf + 3) && isDigit(in_buf + 4))
		 || (*in_buf != '\t' && ((use_isprint && !isPrint(in_buf)) || *(uchar*)in_buf < ' ')))
			ob += snprintf(ob, 6, "\\#%03o", *(uchar*)in_buf++);
		else
			*ob++ = *in_buf++;
	}
	if (end_char) /* The "- 10" above means that there is always room for one more char here. */
		*ob++ = end_char;
	if (ob != outbuf && fwrite(outbuf, ob - outbuf, 1, f) != 1)
		exit_cleanup(RERR_MESSAGEIO);
}

/* this is the underlying (unformatted) rsync debugging function. Call
 * it with FINFO, FERROR_*, FWARNING, FLOG, or FCLIENT.  Note: recursion
 * can happen with certain fatal conditions. */
void rwrite(enum logcode code, const char *buf, int len, int is_utf8)
{
	char trailing_CR_or_NL;
	FILE *f = msgs2stderr == 1 ? stderr : stdout;
#ifdef ICONV_OPTION
	iconv_t ic = is_utf8 && ic_recv != (iconv_t)-1 ? ic_recv : ic_chck;
#else
#ifdef ICONV_CONST
	iconv_t ic = ic_chck;
#endif
#endif

	if (len < 0)
		exit_cleanup(RERR_MESSAGEIO);

	if (msgs2stderr == 1) {
		/* A normal daemon can get msgs2stderr set if the socket is busted, so we
		 * change the message destination into an FLOG message in order to try to
		 * get some info about an abnormal-exit into the log file. An rsh daemon
		 * can have this set via user request, so we'll leave the code alone so
		 * that the msg gets logged and then sent to stderr after that. */
		if (am_daemon > 0 && code != FCLIENT)
			code = FLOG;
	} else if (send_msgs_to_gen) {
		assert(!is_utf8);
		/* Pass the message to our sibling in native charset. */
		send_msg((enum msgcode)code, buf, len, 0);
		return;
	}

	if (code == FERROR_SOCKET) /* This gets simplified for a non-sibling. */
		code = FERROR;
	else if (code == FERROR_UTF8) {
		is_utf8 = 1;
		code = FERROR;
	}

	if (code == FCLIENT)
		code = FINFO;
	else if (am_daemon || logfile_name) {
		static int in_block;
		char msg[2048];
		int priority = code == FINFO || code == FLOG ? LOG_INFO :  LOG_WARNING;

		if (in_block)
			return;
		in_block = 1;
		if (!log_initialised)
			log_init(0);
		strlcpy(msg, buf, MIN((int)sizeof msg, len + 1));
		logit(priority, msg);
		in_block = 0;

		if (code == FLOG || (am_daemon && !am_server))
			return;
	} else if (code == FLOG)
		return;

	switch (code) {
	case FERROR_XFER:
		got_xfer_error = 1;
		/* FALL THROUGH */
	case FERROR:
	case FWARNING:
		f = stderr;
		break;
	case FINFO:
		if (quiet)
			return;
		break;
	/*case FLOG:*/
	/*case FCLIENT:*/
	/*case FERROR_UTF8:*/
	/*case FERROR_SOCKET:*/
	default:
		fprintf(stderr, "Bad logcode in rwrite(): %d [%s]\n", (int)code, who_am_i());
		exit_cleanup(RERR_MESSAGEIO);
	}

	if (am_server && msgs2stderr != 1 && (msgs2stderr != 2 || f != stderr)) {
		enum msgcode msg = (enum msgcode)code;
		if (protocol_version < 30) {
			if (msg == MSG_ERROR)
				msg = MSG_ERROR_XFER;
			else if (msg == MSG_WARNING)
				msg = MSG_INFO;
		}
		/* Pass the message to the non-server side. */
		if (send_msg(msg, buf, len, !is_utf8))
			return;
		if (am_daemon > 0) {
			/* TODO: can we send the error to the user somehow? */
			return;
		}
		f = stderr;
	}

	if (output_needs_newline) {
		fputc('\n', f);
		output_needs_newline = 0;
	}

	trailing_CR_or_NL = len && (buf[len-1] == '\n' || buf[len-1] == '\r') ? buf[--len] : '\0';

	if (len && buf[0] == '\r') {
		fputc('\r', f);
		buf++;
		len--;
	}

#ifdef ICONV_CONST
	if (ic != (iconv_t)-1) {
		xbuf outbuf, inbuf;
		char convbuf[1024];
		int ierrno;

		INIT_CONST_XBUF(outbuf, convbuf);
		INIT_XBUF(inbuf, (char*)buf, len, (size_t)-1);

		while (inbuf.len) {
			iconvbufs(ic, &inbuf, &outbuf, inbuf.pos ? 0 : ICB_INIT);
			ierrno = errno;
			if (outbuf.len) {
				char trailing = inbuf.len ? '\0' : trailing_CR_or_NL;
				filtered_fwrite(f, convbuf, outbuf.len, 0, trailing);
				if (trailing) {
					trailing_CR_or_NL = '\0';
					fflush(f);
				}
				outbuf.len = 0;
			}
			/* Log one byte of illegal/incomplete sequence and continue with
			 * the next character. Check that the buffer is non-empty for the
			 * sake of robustness. */
			if ((ierrno == EILSEQ || ierrno == EINVAL) && inbuf.len) {
				fprintf(f, "\\#%03o", CVAL(inbuf.buf, inbuf.pos++));
				inbuf.len--;
			}
		}

		if (trailing_CR_or_NL) {
			fputc(trailing_CR_or_NL, f);
			fflush(f);
		}
	} else
#endif
	{
		filtered_fwrite(f, buf, len, !allow_8bit_chars, trailing_CR_or_NL);
		if (trailing_CR_or_NL)
			fflush(f);
	}
}

/* This is the rsync debugging function. Call it with FINFO, FERROR_*,
 * FWARNING, FLOG, or FCLIENT. */
void rprintf(enum logcode code, const char *format, ...)
{
	va_list ap;
	char buf[BIGPATHBUFLEN];
	size_t len;

	va_start(ap, format);
	len = vsnprintf(buf, sizeof buf, format, ap);
	va_end(ap);

	/* Deal with buffer overruns.  Instead of panicking, just
	 * truncate the resulting string.  (Note that configure ensures
	 * that we have a vsnprintf() that doesn't ever return -1.) */
	if (len > sizeof buf - 1) {
		static const char ellipsis[] = "[...]";

		/* Reset length, and zero-terminate the end of our buffer */
		len = sizeof buf - 1;
		buf[len] = '\0';

		/* Copy the ellipsis to the end of the string, but give
		 * us one extra character:
		 *
		 *                  v--- null byte at buf[sizeof buf - 1]
		 *        abcdefghij0
		 *     -> abcd[...]00  <-- now two null bytes at end
		 *
		 * If the input format string has a trailing newline,
		 * we copy it into that extra null; if it doesn't, well,
		 * all we lose is one byte.  */
		memcpy(buf+len-sizeof ellipsis, ellipsis, sizeof ellipsis);
		if (format[strlen(format)-1] == '\n') {
			buf[len-1] = '\n';
		}
	}

	rwrite(code, buf, len, 0);
}

/* This is like rprintf, but it also tries to print some
 * representation of the error code.  Normally errcode = errno.
 *
 * Unlike rprintf, this always adds a newline and there should not be
 * one in the format string.
 *
 * Note that since strerror might involve dynamically loading a
 * message catalog we need to call it once before chroot-ing. */
void rsyserr(enum logcode code, int errcode, const char *format, ...)
{
	va_list ap;
	char buf[BIGPATHBUFLEN];
	size_t len;

	len = snprintf(buf, sizeof buf, RSYNC_NAME ": [%s] ", who_am_i());

	va_start(ap, format);
	len += vsnprintf(buf + len, sizeof buf - len, format, ap);
	va_end(ap);

	if (len < sizeof buf) {
		len += snprintf(buf + len, sizeof buf - len,
				": %s (%d)\n", strerror(errcode), errcode);
	}
	if (len >= sizeof buf)
		exit_cleanup(RERR_MESSAGEIO);

	rwrite(code, buf, len, 0);
}

void rflush(enum logcode code)
{
	FILE *f;

	if (am_daemon || code == FLOG)
		return;

	if (!am_server && (code == FINFO || code == FCLIENT))
		f = stdout;
	else
		f = stderr;

	fflush(f);
}

void remember_initial_stats(void)
{
	initial_data_read = total_data_read;
	initial_data_written = total_data_written;
}

/* A generic logging routine for send/recv, with parameter substitiution. */
static void log_formatted(enum logcode code, const char *format, const char *op,
			  struct file_struct *file, const char *fname, int iflags,
			  const char *hlink)
{
	char buf[MAXPATHLEN+1024], buf2[MAXPATHLEN], fmt[32];
	char *p, *s, *c;
	const char *n;
	size_t len, total;
	int64 b;

	*fmt = '%';

	/* We expand % codes one by one in place in buf.  We don't
	 * copy in the terminating null of the inserted strings, but
	 * rather keep going until we reach the null of the format. */
	total = strlcpy(buf, format, sizeof buf);
	if (total > MAXPATHLEN) {
		rprintf(FERROR, "log-format string is WAY too long!\n");
		exit_cleanup(RERR_MESSAGEIO);
	}
	buf[total++] = '\n';
	buf[total] = '\0';

	for (p = buf; (p = strchr(p, '%')) != NULL; ) {
		int humanize = 0;
		s = p++;
		c = fmt + 1;
		while (*p == '\'') {
			humanize++;
			p++;
		}
		if (*p == '-')
			*c++ = *p++;
		while (isDigit(p) && c - fmt < (int)(sizeof fmt) - 8)
			*c++ = *p++;
		while (*p == '\'') {
			humanize++;
			p++;
		}
		if (!*p)
			break;
		*c = '\0';
		n = NULL;

		/* Note for %h and %a: it doesn't matter what fd we pass to
		 * client_{name,addr} because rsync_module will already have
		 * forced the answer to be cached (assuming, of course, for %h
		 * that lp_reverse_lookup(module_id) is true). */
		switch (*p) {
		case 'h':
			if (am_daemon) {
				n = lp_reverse_lookup(module_id)
				  ? client_name(0) : undetermined_hostname;
			}
			break;
		case 'a':
			if (am_daemon)
				n = client_addr(0);
			break;
		case 'l':
			strlcat(fmt, "s", sizeof fmt);
			snprintf(buf2, sizeof buf2, fmt,
				 do_big_num(F_LENGTH(file), humanize, NULL));
			n = buf2;
			break;
		case 'U':
			strlcat(fmt, "u", sizeof fmt);
			snprintf(buf2, sizeof buf2, fmt,
				 uid_ndx ? F_OWNER(file) : 0);
			n = buf2;
			break;
		case 'G':
			if (!gid_ndx || file->flags & FLAG_SKIP_GROUP)
				n = "DEFAULT";
			else {
				strlcat(fmt, "u", sizeof fmt);
				snprintf(buf2, sizeof buf2, fmt,
					 F_GROUP(file));
				n = buf2;
			}
			break;
		case 'p':
			strlcat(fmt, "d", sizeof fmt);
			snprintf(buf2, sizeof buf2, fmt, (int)getpid());
			n = buf2;
			break;
		case 'M':
			n = c = timestring(file->modtime);
			while ((c = strchr(c, ' ')) != NULL)
				*c = '-';
			break;
		case 'B':
			c = buf2 + MAXPATHLEN - PERMSTRING_SIZE - 1;
			permstring(c, file->mode);
			n = c + 1; /* skip the type char */
			break;
		case 'o':
			n = op;
			break;
		case 'f':
			if (fname) {
				c = f_name_buf();
				strlcpy(c, fname, MAXPATHLEN);
			} else
				c = f_name(file, NULL);
			if (am_sender && F_PATHNAME(file)) {
				pathjoin(buf2, sizeof buf2,
					 F_PATHNAME(file), c);
				clean_fname(buf2, 0);
				if (fmt[1]) {
					strlcpy(c, buf2, MAXPATHLEN);
					n = c;
				} else
					n = buf2;
			} else if (am_daemon && *c != '/') {
				pathjoin(buf2, sizeof buf2,
					 curr_dir + module_dirlen, c);
				clean_fname(buf2, 0);
				if (fmt[1]) {
					strlcpy(c, buf2, MAXPATHLEN);
					n = c;
				} else
					n = buf2;
			} else {
				clean_fname(c, 0);
				n = c;
			}
			if (*n == '/')
				n++;
			break;
		case 'n':
			if (fname) {
				c = f_name_buf();
				strlcpy(c, fname, MAXPATHLEN);
			} else
				c = f_name(file, NULL);
			if (S_ISDIR(file->mode))
				strlcat(c, "/", MAXPATHLEN);
			n = c;
			break;
		case 'L':
			if (hlink && *hlink) {
				n = hlink;
				strlcpy(buf2, " => ", sizeof buf2);
			} else if (S_ISLNK(file->mode) && !fname) {
				n = F_SYMLINK(file);
				strlcpy(buf2, " -> ", sizeof buf2);
			} else {
				n = "";
				if (!fmt[1])
					break;
				strlcpy(buf2, "    ", sizeof buf2);
			}
			strlcat(fmt, "s", sizeof fmt);
			snprintf(buf2 + 4, sizeof buf2 - 4, fmt, n);
			n = buf2;
			break;
		case 'm':
			n = lp_name(module_id);
			break;
		case 't':
			n = timestring(time(NULL));
			break;
		case 'P':
			n = full_module_path;
			break;
		case 'u':
			n = auth_user;
			break;
		case 'b':
		case 'c':
			if (!(iflags & ITEM_TRANSFER))
				b = 0;
			else if ((!!am_sender) ^ (*p == 'c'))
				b = total_data_written - initial_data_written;
			else
				b = total_data_read - initial_data_read;
			strlcat(fmt, "s", sizeof fmt);
			snprintf(buf2, sizeof buf2, fmt,
				 do_big_num(b, humanize, NULL));
			n = buf2;
			break;
		case 'C':
			n = NULL;
			if (S_ISREG(file->mode)) {
				if (always_checksum)
					n = sum_as_hex(file_sum_nni->num, F_SUM(file), 1);
				else if (iflags & ITEM_TRANSFER)
					n = sum_as_hex(xfer_sum_nni->num, sender_file_sum, 0);
			}
			if (!n) {
				int sum_len = csum_len_for_type(always_checksum ? file_sum_nni->num : xfer_sum_nni->num,
								always_checksum);
				memset(buf2, ' ', sum_len*2);
				buf2[sum_len*2] = '\0';
				n = buf2;
			}
			break;
		case 'i':
			if (iflags & ITEM_DELETED) {
				n = "*deleting  ";
				break;
			}
			n  = c = buf2 + MAXPATHLEN - 32;
			c[0] = iflags & ITEM_LOCAL_CHANGE
			     ? iflags & ITEM_XNAME_FOLLOWS ? 'h' : 'c'
			     : !(iflags & ITEM_TRANSFER) ? '.'
			     : !local_server && *op == 's' ? '<' : '>';
			if (S_ISLNK(file->mode)) {
				c[1] = 'L';
				c[3] = '.';
				c[4] = !(iflags & ITEM_REPORT_TIME) ? '.'
				     : !preserve_mtimes || !receiver_symlink_times
				    || (iflags & ITEM_REPORT_TIMEFAIL) ? 'T' : 't';
			} else {
				c[1] = S_ISDIR(file->mode) ? 'd'
				     : IS_SPECIAL(file->mode) ? 'S'
				     : IS_DEVICE(file->mode) ? 'D' : 'f';
				c[3] = !(iflags & ITEM_REPORT_SIZE) ? '.' : 's';
				c[4] = !(iflags & ITEM_REPORT_TIME) ? '.'
				     : !preserve_mtimes ? 'T' : 't';
			}
			c[2] = !(iflags & ITEM_REPORT_CHANGE) ? '.' : 'c';
			c[5] = !(iflags & ITEM_REPORT_PERMS) ? '.' : 'p';
			c[6] = !(iflags & ITEM_REPORT_OWNER) ? '.' : 'o';
			c[7] = !(iflags & ITEM_REPORT_GROUP) ? '.' : 'g';
			c[8] = !(iflags & (ITEM_REPORT_ATIME|ITEM_REPORT_CRTIME)) ? '.'
			     : BITS_SET(iflags, ITEM_REPORT_ATIME|ITEM_REPORT_CRTIME) ? 'b'
			     : iflags & ITEM_REPORT_ATIME ? 'u' : 'n';
			c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
			c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
			c[11] = '\0';

			if (iflags & (ITEM_IS_NEW|ITEM_MISSING_DATA)) {
				char ch = iflags & ITEM_IS_NEW ? '+' : '?';
				int i;
				for (i = 2; c[i]; i++)
					c[i] = ch;
			} else if (c[0] == '.' || c[0] == 'h' || c[0] == 'c') {
				int i;
				for (i = 2; c[i]; i++) {
					if (c[i] != '.')
						break;
				}
				if (!c[i]) {
					for (i = 2; c[i]; i++)
						c[i] = ' ';
				}
			}
			break;
		}

		/* "n" is the string to be inserted in place of this % code. */
		if (!n)
			continue;
		if (n != buf2 && fmt[1]) {
			strlcat(fmt, "s", sizeof fmt);
			snprintf(buf2, sizeof buf2, fmt, n);
			n = buf2;
		}
		len = strlen(n);

		/* Subtract the length of the escape from the string's size. */
		total -= p - s + 1;

		if (len + total >= (size_t)sizeof buf) {
			rprintf(FERROR,
				"buffer overflow expanding %%%c -- exiting\n",
				p[0]);
			exit_cleanup(RERR_MESSAGEIO);
		}

		/* Shuffle the rest of the string along to make space for n */
		if (len != (size_t)(p - s + 1))
			memmove(s + len, p + 1, total - (s - buf) + 1);
		total += len;

		/* Insert the contents of string "n", but NOT its null. */
		if (len)
			memcpy(s, n, len);

		/* Skip over inserted string; continue looking */
		p = s + len;
	}

	rwrite(code, buf, total, 0);
}

/* Return 1 if the format escape is in the log-format string (e.g. look for
 * the 'b' in the "%9b" format escape). */
int log_format_has(const char *format, char esc)
{
	const char *p;

	if (!format)
		return 0;

	for (p = format; (p = strchr(p, '%')) != NULL; ) {
		for (p++; *p == '\''; p++) {} /*SHARED ITERATOR*/
		if (*p == '-')
			p++;
		while (isDigit(p))
			p++;
		while (*p == '\'') p++;
		if (!*p)
			break;
		if (*p == esc)
			return 1;
	}
	return 0;
}

/* Log the transfer of a file.  If the code is FCLIENT, the output just goes
 * to stdout.  If it is FLOG, it just goes to the log file.  Otherwise we
 * output to both. */
void log_item(enum logcode code, struct file_struct *file, int iflags, const char *hlink)
{
	const char *s_or_r = am_sender ? "send" : "recv";

	if (code != FLOG && stdout_format && !am_server)
		log_formatted(FCLIENT, stdout_format, s_or_r, file, NULL, iflags, hlink);
	if (code != FCLIENT && logfile_format && *logfile_format)
		log_formatted(FLOG, logfile_format, s_or_r, file, NULL, iflags, hlink);
}

void maybe_log_item(struct file_struct *file, int iflags, int itemizing, const char *buf)
{
	int significant_flags = iflags & SIGNIFICANT_ITEM_FLAGS;
	int see_item = itemizing && (significant_flags || *buf
		|| stdout_format_has_i > 1 || (INFO_GTE(NAME, 2) && stdout_format_has_i));
	int local_change = iflags & ITEM_LOCAL_CHANGE && significant_flags;
	if (am_server) {
		if (logfile_name && !dry_run && see_item
		 && (significant_flags || logfile_format_has_i))
			log_item(FLOG, file, iflags, buf);
	} else if (see_item || local_change || *buf
	    || (S_ISDIR(file->mode) && significant_flags)) {
		enum logcode code = significant_flags || logfile_format_has_i ? FINFO : FCLIENT;
		log_item(code, file, iflags, buf);
	}
}

void log_delete(const char *fname, int mode)
{
	static struct file_struct *file = NULL;
	int len = strlen(fname);
	const char *fmt;

	if (!file) {
		int extra_len = (file_extra_cnt + 2) * EXTRA_LEN;
		char *bp;
#if EXTRA_ROUNDING > 0
		if (extra_len & (EXTRA_ROUNDING * EXTRA_LEN))
			extra_len = (extra_len | (EXTRA_ROUNDING * EXTRA_LEN)) + EXTRA_LEN;
#endif

		bp = new_array0(char, FILE_STRUCT_LEN + extra_len + 1);
		bp += extra_len;
		file = (struct file_struct *)bp;
	}

	file->mode = mode;

	if (am_server && protocol_version >= 29 && len < MAXPATHLEN) {
		if (S_ISDIR(mode))
			len++; /* directories include trailing null */
		send_msg(MSG_DELETED, fname, len, am_generator);
	} else if (!INFO_GTE(DEL, 1) && !stdout_format)
		;
	else {
		fmt = stdout_format_has_o_or_i ? stdout_format : "deleting %n";
		log_formatted(FCLIENT, fmt, "del.", file, fname, ITEM_DELETED, NULL);
	}

	if (!logfile_name || dry_run || !logfile_format)
		return;

	fmt = logfile_format_has_o_or_i ? logfile_format : "deleting %n";
	log_formatted(FLOG, fmt, "del.", file, fname, ITEM_DELETED, NULL);
}

/*
 * Called when the transfer is interrupted for some reason.
 *
 * Code is one of the RERR_* codes from errcode.h, or terminating
 * successfully.
 */
void log_exit(int code, const char *file, int line)
{
	/* The receiving side's stats are split between 2 procs until the
	 * end of the run, so only the sender can output non-final info. */
	if (code == 0 || am_sender) {
		rprintf(FLOG,"sent %s bytes  received %s bytes  total size %s\n",
			big_num(stats.total_written),
			big_num(stats.total_read),
			big_num(stats.total_size));
	}
	if (code != 0 && am_server != 2) {
		const char *name;

		name = rerr_name(code);
		if (!name)
			name = "unexplained error";

		/* VANISHED is not an error, only a warning */
		if (code == RERR_VANISHED) {
			rprintf(FWARNING, "rsync warning: %s (code %d) at %s(%d) [%s=%s]\n",
				name, code, src_file(file), line, who_am_i(), rsync_version());
		} else {
			rprintf(FERROR, "rsync error: %s (code %d) at %s(%d) [%s=%s]\n",
				name, code, src_file(file), line, who_am_i(), rsync_version());
		}
	}
}
/*
 * The startup routines, including main(), for rsync.
 *
 * Copyright (C) 1996-2001 Andrew Tridgell <tridge@samba.org>
 * Copyright (C) 1996 Paul Mackerras
 * Copyright (C) 2001, 2002 Martin Pool <mbp@samba.org>
 * Copyright (C) 2003-2022 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"
#include "inums.h"
#include "ifuncs.h"
#include "io.h"
#if defined CONFIG_LOCALE && defined HAVE_LOCALE_H
#include <locale.h>
#endif
#include <popt.h>
#ifdef __TANDEM
#include <floss.h(floss_execlp)>
#endif

extern int dry_run;
extern int list_only;
extern int io_timeout;
extern int am_root;
extern int am_server;
extern int am_sender;
extern int am_daemon;
extern int inc_recurse;
extern int blocking_io;
extern int always_checksum;
extern int remove_source_files;
extern int output_needs_newline;
extern int called_from_signal_handler;
extern int need_messages_from_generator;
extern int kluge_around_eof;
extern int got_xfer_error;
extern int old_style_args;
extern int msgs2stderr;
extern int module_id;
extern int read_only;
extern int copy_links;
extern int copy_dirlinks;
extern int copy_unsafe_links;
extern int keep_dirlinks;
extern int preserve_hard_links;
extern int protocol_version;
extern int mkpath_dest_arg;
extern int file_total;
extern int recurse;
extern int xfer_dirs;
extern int protect_args;
extern int relative_paths;
extern int sanitize_paths;
extern int curr_dir_depth;
extern unsigned int curr_dir_len;
extern int module_id;
extern int rsync_port;
extern int whole_file;
extern int read_batch;
extern int write_batch;
extern int batch_fd;
extern int sock_f_in;
extern int sock_f_out;
extern int filesfrom_fd;
extern int connect_timeout;
extern int send_msgs_to_gen;
extern dev_t filesystem_dev;
extern pid_t cleanup_child_pid;
extern size_t bwlimit_writemax;
extern unsigned int module_dirlen;
extern BOOL flist_receiving_enabled;
extern BOOL want_progress_now;
extern BOOL shutting_down;
extern int backup_dir_len;
extern int basis_dir_cnt;
extern int default_af_hint;
extern int stdout_format_has_i;
extern int trust_sender_filter;
extern int trust_sender_args;
extern struct stats stats;
extern char *stdout_format;
extern char *logfile_format;
extern char *filesfrom_host;
extern char *partial_dir;
extern char *rsync_path;
extern char *shell_cmd;
extern char *password_file;
extern char *backup_dir;
extern char *copy_as;
extern char *tmpdir;
extern char curr_dir[MAXPATHLEN];
extern char backup_dir_buf[MAXPATHLEN];
extern char *basis_dir[MAX_BASIS_DIRS+1];
extern struct file_list *first_flist;
extern filter_rule_list daemon_filter_list, implied_filter_list;

uid_t our_uid;
gid_t our_gid;
int am_receiver = 0;  /* Only set to 1 after the receiver/generator fork. */
int am_generator = 0; /* Only set to 1 after the receiver/generator fork. */
int local_server = 0;
int daemon_connection = 0; /* 0 = no daemon, 1 = daemon via remote shell, -1 = daemon via socket */
mode_t orig_umask = 0;
int batch_gen_fd = -1;
int sender_keeps_checksum = 0;
int raw_argc, cooked_argc;
char **raw_argv, **cooked_argv;

/* There's probably never more than at most 2 outstanding child processes,
 * but set it higher, just in case. */
#define MAXCHILDPROCS 7

#ifdef HAVE_SIGACTION
# ifdef HAVE_SIGPROCMASK
#  define SIGACTMASK(n,h) SIGACTION(n,h), sigaddset(&sigmask,(n))
# else
#  define SIGACTMASK(n,h) SIGACTION(n,h)
# endif
static struct sigaction sigact;
#endif

struct pid_status {
	pid_t pid;
	int status;
} pid_stat_table[MAXCHILDPROCS];

static time_t starttime, endtime;
static int64 total_read, total_written;

static void show_malloc_stats(void);

/* Works like waitpid(), but if we already harvested the child pid in our
 * remember_children(), we succeed instead of returning an error. */
pid_t wait_process(pid_t pid, int *status_ptr, int flags)
{
	pid_t waited_pid;

	do {
		waited_pid = waitpid(pid, status_ptr, flags);
	} while (waited_pid == -1 && errno == EINTR);

	if (waited_pid == -1 && errno == ECHILD) {
		/* Status of requested child no longer available:  check to
		 * see if it was processed by remember_children(). */
		int cnt;
		for (cnt = 0; cnt < MAXCHILDPROCS; cnt++) {
			if (pid == pid_stat_table[cnt].pid) {
				*status_ptr = pid_stat_table[cnt].status;
				pid_stat_table[cnt].pid = 0;
				return pid;
			}
		}
	}

	return waited_pid;
}

int shell_exec(const char *cmd)
{
	char *shell = getenv("RSYNC_SHELL");
	int status;
	pid_t pid;

	if (!shell)
		return system(cmd);

	if ((pid = fork()) < 0)
		return -1;

	if (pid == 0) {
		execlp(shell, shell, "-c", cmd, NULL);
		_exit(1);
	}

	int ret = wait_process(pid, &status, 0);
	return ret < 0 ? -1 : status;
}

/* Wait for a process to exit, calling io_flush while waiting. */
static void wait_process_with_flush(pid_t pid, int *exit_code_ptr)
{
	pid_t waited_pid;
	int status;

	while ((waited_pid = wait_process(pid, &status, WNOHANG)) == 0) {
		msleep(20);
		io_flush(FULL_FLUSH);
	}

	/* TODO: If the child exited on a signal, then log an
	 * appropriate error message.  Perhaps we should also accept a
	 * message describing the purpose of the child.  Also indicate
	 * this to the caller so that they know something went wrong. */
	if (waited_pid < 0) {
		rsyserr(FERROR, errno, "waitpid");
		*exit_code_ptr = RERR_WAITCHILD;
	} else if (!WIFEXITED(status)) {
#ifdef WCOREDUMP
		if (WCOREDUMP(status))
			*exit_code_ptr = RERR_CRASHED;
		else
#endif
		if (WIFSIGNALED(status))
			*exit_code_ptr = RERR_TERMINATED;
		else
			*exit_code_ptr = RERR_WAITCHILD;
	} else
		*exit_code_ptr = WEXITSTATUS(status);
}

void write_del_stats(int f)
{
	if (read_batch)
		write_int(f, NDX_DEL_STATS);
	else
		write_ndx(f, NDX_DEL_STATS);
	write_varint(f, stats.deleted_files - stats.deleted_dirs
		      - stats.deleted_symlinks - stats.deleted_devices
		      - stats.deleted_specials);
	write_varint(f, stats.deleted_dirs);
	write_varint(f, stats.deleted_symlinks);
	write_varint(f, stats.deleted_devices);
	write_varint(f, stats.deleted_specials);
}

void read_del_stats(int f)
{
	stats.deleted_files = read_varint(f);
	stats.deleted_files += stats.deleted_dirs = read_varint(f);
	stats.deleted_files += stats.deleted_symlinks = read_varint(f);
	stats.deleted_files += stats.deleted_devices = read_varint(f);
	stats.deleted_files += stats.deleted_specials = read_varint(f);
}

static void become_copy_as_user(void)
{
	char *gname;
	uid_t uid;
	gid_t gid;

	if (!copy_as)
		return;

	if (DEBUG_GTE(CMD, 2))
		rprintf(FINFO, "[%s] copy_as=%s\n", who_am_i(), copy_as);

	if ((gname = strchr(copy_as, ':')) != NULL)
		*gname++ = '\0';

	if (!user_to_uid(copy_as, &uid, True)) {
		rprintf(FERROR, "Invalid copy-as user: %s\n", copy_as);
		exit_cleanup(RERR_SYNTAX);
	}

	if (gname) {
		if (!group_to_gid(gname, &gid, True)) {
			rprintf(FERROR, "Invalid copy-as group: %s\n", gname);
			exit_cleanup(RERR_SYNTAX);
		}
	} else {
		struct passwd *pw;
		if ((pw = getpwuid(uid)) == NULL) {
			rsyserr(FERROR, errno, "getpwuid failed");
			exit_cleanup(RERR_SYNTAX);
		}
		gid = pw->pw_gid;
	}

	if (setgid(gid) < 0) {
		rsyserr(FERROR, errno, "setgid failed");
		exit_cleanup(RERR_SYNTAX);
	}
#ifdef HAVE_SETGROUPS
	if (setgroups(1, &gid)) {
		rsyserr(FERROR, errno, "setgroups failed");
		exit_cleanup(RERR_SYNTAX);
	}
#endif
#ifdef HAVE_INITGROUPS
	if (!gname && initgroups(copy_as, gid) < 0) {
		rsyserr(FERROR, errno, "initgroups failed");
		exit_cleanup(RERR_SYNTAX);
	}
#endif

	if (setuid(uid) < 0
#ifdef HAVE_SETEUID
	 || seteuid(uid) < 0
#endif
	) {
		rsyserr(FERROR, errno, "setuid failed");
		exit_cleanup(RERR_SYNTAX);
	}

	our_uid = MY_UID();
	our_gid = MY_GID();
	am_root = (our_uid == ROOT_UID);

	if (gname)
		gname[-1] = ':';
}

/* This function gets called from all 3 processes.  We want the client side
 * to actually output the text, but the sender is the only process that has
 * all the stats we need.  So, if we're a client sender, we do the report.
 * If we're a server sender, we write the stats on the supplied fd.  If
 * we're the client receiver we read the stats from the supplied fd and do
 * the report.  All processes might also generate a set of debug stats, if
 * the verbose level is high enough (this is the only thing that the
 * generator process and the server receiver ever do here). */
static void handle_stats(int f)
{
	endtime = time(NULL);

	/* Cache two stats because the read/write code can change it. */
	total_read = stats.total_read;
	total_written = stats.total_written;

	if (INFO_GTE(STATS, 3)) {
		/* These come out from every process */
		show_malloc_stats();
		show_flist_stats();
	}

	if (am_generator)
		return;

	if (am_daemon) {
		if (f == -1 || !am_sender)
			return;
	}

	if (am_server) {
		if (am_sender) {
			write_varlong30(f, total_read, 3);
			write_varlong30(f, total_written, 3);
			write_varlong30(f, stats.total_size, 3);
			if (protocol_version >= 29) {
				write_varlong30(f, stats.flist_buildtime, 3);
				write_varlong30(f, stats.flist_xfertime, 3);
			}
		}
		return;
	}

	/* this is the client */

	if (f < 0 && !am_sender) /* e.g. when we got an empty file list. */
		;
	else if (!am_sender) {
		/* Read the first two in opposite order because the meaning of
		 * read/write swaps when switching from sender to receiver. */
		total_written = read_varlong30(f, 3);
		total_read = read_varlong30(f, 3);
		stats.total_size = read_varlong30(f, 3);
		if (protocol_version >= 29) {
			stats.flist_buildtime = read_varlong30(f, 3);
			stats.flist_xfertime = read_varlong30(f, 3);
		}
	} else if (write_batch) {
		/* The --read-batch process is going to be a client
		 * receiver, so we need to give it the stats. */
		write_varlong30(batch_fd, total_read, 3);
		write_varlong30(batch_fd, total_written, 3);
		write_varlong30(batch_fd, stats.total_size, 3);
		if (protocol_version >= 29) {
			write_varlong30(batch_fd, stats.flist_buildtime, 3);
			write_varlong30(batch_fd, stats.flist_xfertime, 3);
		}
	}
}

static void output_itemized_counts(const char *prefix, int *counts)
{
	static char *labels[] = { "reg", "dir", "link", "dev", "special" };
	char buf[1024], *pre = " (";
	int j, len = 0;
	int total = counts[0];
	if (total) {
		counts[0] -= counts[1] + counts[2] + counts[3] + counts[4];
		for (j = 0; j < 5; j++) {
			if (counts[j]) {
				len += snprintf(buf+len, sizeof buf - len - 2,
					"%s%s: %s",
					pre, labels[j], comma_num(counts[j]));
				pre = ", ";
			}
		}
		buf[len++] = ')';
	}
	buf[len] = '\0';
	rprintf(FINFO, "%s: %s%s\n", prefix, comma_num(total), buf);
}

static const char *bytes_per_sec_human_dnum(void)
{
	if (starttime == (time_t)-1 || endtime == (time_t)-1)
		return "UNKNOWN";
	return human_dnum((total_written + total_read) / (0.5 + (endtime - starttime)), 2);
}

static void output_summary(void)
{
	if (INFO_GTE(STATS, 2)) {
		rprintf(FCLIENT, "\n");
		output_itemized_counts("Number of files", &stats.num_files);
		if (protocol_version >= 29)
			output_itemized_counts("Number of created files", &stats.created_files);
		if (protocol_version >= 31)
			output_itemized_counts("Number of deleted files", &stats.deleted_files);
		rprintf(FINFO,"Number of regular files transferred: %s\n",
			comma_num(stats.xferred_files));
		rprintf(FINFO,"Total file size: %s bytes\n",
			human_num(stats.total_size));
		rprintf(FINFO,"Total transferred file size: %s bytes\n",
			human_num(stats.total_transferred_size));
		rprintf(FINFO,"Literal data: %s bytes\n",
			human_num(stats.literal_data));
		rprintf(FINFO,"Matched data: %s bytes\n",
			human_num(stats.matched_data));
		rprintf(FINFO,"File list size: %s\n",
			human_num(stats.flist_size));
		if (stats.flist_buildtime) {
			rprintf(FINFO,
				"File list generation time: %s seconds\n",
				comma_dnum((double)stats.flist_buildtime / 1000, 3));
			rprintf(FINFO,
				"File list transfer time: %s seconds\n",
				comma_dnum((double)stats.flist_xfertime / 1000, 3));
		}
		rprintf(FINFO,"Total bytes sent: %s\n",
			human_num(total_written));
		rprintf(FINFO,"Total bytes received: %s\n",
			human_num(total_read));
	}

	if (INFO_GTE(STATS, 1)) {
		rprintf(FCLIENT, "\n");
		rprintf(FINFO,
			"sent %s bytes  received %s bytes  %s bytes/sec\n",
			human_num(total_written), human_num(total_read),
			bytes_per_sec_human_dnum());
		rprintf(FINFO, "total size is %s  speedup is %s%s\n",
			human_num(stats.total_size),
			comma_dnum((double)stats.total_size / (total_written+total_read), 2),
			write_batch < 0 ? " (BATCH ONLY)" : dry_run ? " (DRY RUN)" : "");
	}

	fflush(stdout);
	fflush(stderr);
}


/**
 * If our C library can get malloc statistics, then show them to FINFO
 **/
static void show_malloc_stats(void)
{
#ifdef MEM_ALLOC_INFO
	struct MEM_ALLOC_INFO mi = MEM_ALLOC_INFO(); /* mallinfo or mallinfo2 */

	rprintf(FCLIENT, "\n");
	rprintf(FINFO, RSYNC_NAME "[%d] (%s%s%s) heap statistics:\n",
		(int)getpid(), am_server ? "server " : "",
		am_daemon ? "daemon " : "", who_am_i());

#define PRINT_ALLOC_NUM(title, descr, num) \
	rprintf(FINFO, "  %-11s%10" SIZE_T_FMT_MOD "d   (" descr ")\n", \
		title ":", (SIZE_T_FMT_CAST)(num));

	PRINT_ALLOC_NUM("arena", "bytes from sbrk", mi.arena);
	PRINT_ALLOC_NUM("ordblks", "chunks not in use", mi.ordblks);
	PRINT_ALLOC_NUM("smblks", "free fastbin blocks", mi.smblks);
	PRINT_ALLOC_NUM("hblks", "chunks from mmap", mi.hblks);
	PRINT_ALLOC_NUM("hblkhd", "bytes from mmap", mi.hblkhd);
	PRINT_ALLOC_NUM("allmem", "bytes from sbrk + mmap", mi.arena + mi.hblkhd);
	PRINT_ALLOC_NUM("usmblks", "always 0", mi.usmblks);
	PRINT_ALLOC_NUM("fsmblks", "bytes in freed fastbin blocks", mi.fsmblks);
	PRINT_ALLOC_NUM("uordblks", "bytes used", mi.uordblks);
	PRINT_ALLOC_NUM("fordblks", "bytes free", mi.fordblks);
	PRINT_ALLOC_NUM("keepcost", "bytes in releasable chunk", mi.keepcost);

#undef PRINT_ALLOC_NUM

#endif /* MEM_ALLOC_INFO */
}


/* Start the remote shell.   cmd may be NULL to use the default. */
static pid_t do_cmd(char *cmd, char *machine, char *user, char **remote_argv, int remote_argc,
		    int *f_in_p, int *f_out_p)
{
	int i, argc = 0;
	char *args[MAX_ARGS], *need_to_free = NULL;
	pid_t pid;
	int dash_l_set = 0;

	if (!read_batch && !local_server) {
		char *t, *f, in_quote = '\0';
		char *rsh_env = getenv(RSYNC_RSH_ENV);
		if (!cmd)
			cmd = rsh_env;
		if (!cmd)
			cmd = RSYNC_RSH;
		cmd = need_to_free = strdup(cmd);

		for (t = f = cmd; *f; f++) {
			if (*f == ' ')
				continue;
			/* Comparison leaves rooms for server_options(). */
			if (argc >= MAX_ARGS - MAX_SERVER_ARGS)
				goto arg_overflow;
			args[argc++] = t;
			while (*f != ' ' || in_quote) {
				if (!*f) {
					if (in_quote) {
						rprintf(FERROR,
							"Missing trailing-%c in remote-shell command.\n",
							in_quote);
						exit_cleanup(RERR_SYNTAX);
					}
					f--;
					break;
				}
				if (*f == '\'' || *f == '"') {
					if (!in_quote) {
						in_quote = *f++;
						continue;
					}
					if (*f == in_quote && *++f != in_quote) {
						in_quote = '\0';
						continue;
					}
				}
				*t++ = *f++;
			}
			*t++ = '\0';
		}

		/* NOTE: must preserve t == start of command name until the end of the args handling! */
		if ((t = strrchr(cmd, '/')) != NULL)
			t++;
		else
			t = cmd;

		/* Check to see if we've already been given '-l user' in the remote-shell command. */
		for (i = 0; i < argc-1; i++) {
			if (!strcmp(args[i], "-l") && args[i+1][0] != '-')
				dash_l_set = 1;
		}

#ifdef HAVE_REMSH
		/* remsh (on HPUX) takes the arguments the other way around */
		args[argc++] = machine;
		if (user && !(daemon_connection && dash_l_set)) {
			args[argc++] = "-l";
			args[argc++] = user;
		}
#else
		if (user && !(daemon_connection && dash_l_set)) {
			args[argc++] = "-l";
			args[argc++] = user;
		}
#ifdef AF_INET
		if (default_af_hint == AF_INET && strcmp(t, "ssh") == 0)
			args[argc++] = "-4"; /* we're using ssh so we can add a -4 option */
#endif
#ifdef AF_INET6
		if (default_af_hint == AF_INET6 && strcmp(t, "ssh") == 0)
			args[argc++] = "-6"; /* we're using ssh so we can add a -6 option */
#endif
		args[argc++] = machine;
#endif

		args[argc++] = rsync_path;

		if (blocking_io < 0 && (strcmp(t, "rsh") == 0 || strcmp(t, "remsh") == 0))
			blocking_io = 1;

		if (daemon_connection > 0) {
			args[argc++] = "--server";
			args[argc++] = "--daemon";
		} else
			server_options(args, &argc);

		if (argc >= MAX_ARGS - 2)
			goto arg_overflow;
	}

	args[argc++] = ".";

	if (!daemon_connection) {
		while (remote_argc > 0) {
			if (argc >= MAX_ARGS - 1) {
			  arg_overflow:
				rprintf(FERROR, "internal: args[] overflowed in do_cmd()\n");
				exit_cleanup(RERR_SYNTAX);
			}
			args[argc++] = safe_arg(NULL, *remote_argv++);
			remote_argc--;
		}
	}

	args[argc] = NULL;

	if (DEBUG_GTE(CMD, 2)) {
		for (i = 0; i < argc; i++)
			rprintf(FCLIENT, "cmd[%d]=%s ", i, args[i]);
		rprintf(FCLIENT, "\n");
	}

	if (read_batch) {
		int from_gen_pipe[2];
		set_allow_inc_recurse();
		if (fd_pair(from_gen_pipe) < 0) {
			rsyserr(FERROR, errno, "pipe");
			exit_cleanup(RERR_IPC);
		}
		batch_gen_fd = from_gen_pipe[0];
		*f_out_p = from_gen_pipe[1];
		*f_in_p = batch_fd;
		pid = (pid_t)-1; /* no child pid */
#ifdef ICONV_CONST
		setup_iconv();
#endif
	} else if (local_server) {
		/* If the user didn't request --[no-]whole-file, force
		 * it on, but only if we're not batch processing. */
		if (whole_file < 0 && !write_batch)
			whole_file = 1;
		set_allow_inc_recurse();
		pid = local_child(argc, args, f_in_p, f_out_p, child_main);
#ifdef ICONV_CONST
		setup_iconv();
#endif
	} else {
		pid = piped_child(args, f_in_p, f_out_p);
#ifdef ICONV_CONST
		setup_iconv();
#endif
		if (protect_args && !daemon_connection)
			send_protected_args(*f_out_p, args);
	}

	if (need_to_free)
		free(need_to_free);

	return pid;
}

/* Older versions turn an empty string as a reference to the current directory.
 * We now treat this as an error unless --old-args was used. */
static char *dot_dir_or_error()
{
	if (old_style_args || am_server)
		return ".";
	rprintf(FERROR, "Empty destination arg specified (use \".\" or see --old-args).\n");
	exit_cleanup(RERR_SYNTAX);
}

/* The receiving side operates in one of two modes:
 *
 * 1. it receives any number of files into a destination directory,
 * placing them according to their names in the file-list.
 *
 * 2. it receives a single file and saves it using the name in the
 * destination path instead of its file-list name.  This requires a
 * "local name" for writing out the destination file.
 *
 * So, our task is to figure out what mode/local-name we need.
 * For mode 1, we change into the destination directory and return NULL.
 * For mode 2, we change into the directory containing the destination
 * file (if we aren't already there) and return the local-name. */
static char *get_local_name(struct file_list *flist, char *dest_path)
{
	STRUCT_STAT st;
	int statret, trailing_slash;
	char *cp;

	if (DEBUG_GTE(RECV, 1)) {
		rprintf(FINFO, "get_local_name count=%d %s\n",
			file_total, NS(dest_path));
	}

	if (!dest_path || list_only)
		return NULL;

	if (!*dest_path)
		dest_path = dot_dir_or_error();

	if (daemon_filter_list.head) {
		char *slash = strrchr(dest_path, '/');
		if (slash && (slash[1] == '\0' || (slash[1] == '.' && slash[2] == '\0')))
			*slash = '\0';
		else
			slash = NULL;
		if ((*dest_path != '.' || dest_path[1] != '\0')
		 && (check_filter(&daemon_filter_list, FLOG, dest_path, 0) < 0
		  || check_filter(&daemon_filter_list, FLOG, dest_path, 1) < 0)) {
			rprintf(FERROR, "ERROR: daemon has excluded destination \"%s\"\n",
				dest_path);
			exit_cleanup(RERR_FILESELECT);
		}
		if (slash)
			*slash = '/';
	}

	/* See what currently exists at the destination. */
	statret = do_stat(dest_path, &st);
	cp = strrchr(dest_path, '/');
	trailing_slash = cp && !cp[1];

	if (mkpath_dest_arg && statret < 0 && (cp || file_total > 1)) {
		int save_errno = errno;
		int ret = make_path(dest_path, file_total > 1 && !trailing_slash ? 0 : MKP_DROP_NAME);
		if (ret < 0)
			goto mkdir_error;
		if (ret && (INFO_GTE(NAME, 1) || stdout_format_has_i)) {
			if (file_total == 1 || trailing_slash)
				*cp = '\0';
			rprintf(FINFO, "created %d director%s for %s\n", ret, ret == 1 ? "y" : "ies", dest_path);
			if (file_total == 1 || trailing_slash)
				*cp = '/';
		}
		if (ret)
			statret = do_stat(dest_path, &st);
		else
			errno = save_errno;
	}

	if (statret == 0) {
		/* If the destination is a dir, enter it and use mode 1. */
		if (S_ISDIR(st.st_mode)) {
			if (!change_dir(dest_path, CD_NORMAL)) {
				rsyserr(FERROR, errno, "change_dir#1 %s failed",
					full_fname(dest_path));
				exit_cleanup(RERR_FILESELECT);
			}
			filesystem_dev = st.st_dev; /* ensures --force works right w/-x */
			return NULL;
		}
		if (file_total > 1) {
			rprintf(FERROR,
				"ERROR: destination must be a directory when"
				" copying more than 1 file\n");
			exit_cleanup(RERR_FILESELECT);
		}
		if (file_total == 1 && S_ISDIR(flist->files[0]->mode)) {
			rprintf(FERROR,
				"ERROR: cannot overwrite non-directory"
				" with a directory\n");
			exit_cleanup(RERR_FILESELECT);
		}
	} else if (errno != ENOENT) {
		/* If we don't know what's at the destination, fail. */
		rsyserr(FERROR, errno, "ERROR: cannot stat destination %s",
			full_fname(dest_path));
		exit_cleanup(RERR_FILESELECT);
	}

	/* If we need a destination directory because the transfer is not
	 * of a single non-directory or the user has requested one via a
	 * destination path ending in a slash, create one and use mode 1. */
	if (file_total > 1 || trailing_slash) {
		if (trailing_slash)
			*cp = '\0'; /* Lop off the final slash (if any). */

		if (statret == 0) {
			rprintf(FERROR, "ERROR: destination path is not a directory\n");
			exit_cleanup(RERR_SYNTAX);
		}

		if (do_mkdir(dest_path, ACCESSPERMS) != 0) {
		    mkdir_error:
			rsyserr(FERROR, errno, "mkdir %s failed",
				full_fname(dest_path));
			exit_cleanup(RERR_FILEIO);
		}

		if (flist->high >= flist->low
		 && strcmp(flist->files[flist->low]->basename, ".") == 0)
			flist->files[0]->flags |= FLAG_DIR_CREATED;

		if (INFO_GTE(NAME, 1) || stdout_format_has_i)
			rprintf(FINFO, "created directory %s\n", dest_path);

		if (dry_run) {
			/* Indicate that dest dir doesn't really exist. */
			dry_run++;
		}

		if (!change_dir(dest_path, dry_run > 1 ? CD_SKIP_CHDIR : CD_NORMAL)) {
			rsyserr(FERROR, errno, "change_dir#2 %s failed",
				full_fname(dest_path));
			exit_cleanup(RERR_FILESELECT);
		}

		return NULL;
	}

	/* Otherwise, we are writing a single file, possibly on top of an
	 * existing non-directory.  Change to the item's parent directory
	 * (if it has a path component), return the basename of the
	 * destination file as the local name, and use mode 2. */
	if (!cp)
		return dest_path;

	if (cp == dest_path)
		dest_path = "/";

	*cp = '\0';
	if (!change_dir(dest_path, CD_NORMAL)) {
		rsyserr(FERROR, errno, "change_dir#3 %s failed",
			full_fname(dest_path));
		exit_cleanup(RERR_FILESELECT);
	}
	*cp = '/';

	return cp + 1;
}

/* This function checks on our alternate-basis directories.  If we're in
 * dry-run mode and the destination dir does not yet exist, we'll try to
 * tweak any dest-relative paths to make them work for a dry-run (the
 * destination dir must be in curr_dir[] when this function is called).
 * We also warn about any arg that is non-existent or not a directory. */
static void check_alt_basis_dirs(void)
{
	STRUCT_STAT st;
	char *slash = strrchr(curr_dir, '/');
	int j;

	for (j = 0; j < basis_dir_cnt; j++) {
		char *bdir = basis_dir[j];
		int bd_len = strlen(bdir);
		if (bd_len > 1 && bdir[bd_len-1] == '/')
			bdir[--bd_len] = '\0';
		if (dry_run > 1 && *bdir != '/') {
			int len = curr_dir_len + 1 + bd_len + 1;
			char *new = new_array(char, len);
			if (slash && strncmp(bdir, "../", 3) == 0) {
				/* We want to remove only one leading "../" prefix for
				 * the directory we couldn't create in dry-run mode:
				 * this ensures that any other ".." references get
				 * evaluated the same as they would for a live copy. */
				*slash = '\0';
				pathjoin(new, len, curr_dir, bdir + 3);
				*slash = '/';
			} else
				pathjoin(new, len, curr_dir, bdir);
			basis_dir[j] = bdir = new;
		}
		if (do_stat(bdir, &st) < 0)
			rprintf(FWARNING, "%s arg does not exist: %s\n", alt_dest_opt(0), bdir);
		else if (!S_ISDIR(st.st_mode))
			rprintf(FWARNING, "%s arg is not a dir: %s\n", alt_dest_opt(0), bdir);
	}
}

/* This is only called by the sender. */
static void read_final_goodbye(int f_in, int f_out)
{
	int i, iflags, xlen;
	uchar fnamecmp_type;
	char xname[MAXPATHLEN];

	shutting_down = True;

	if (protocol_version < 29)
		i = read_int(f_in);
	else {
		i = read_ndx_and_attrs(f_in, f_out, &iflags, &fnamecmp_type, xname, &xlen);
		if (protocol_version >= 31 && i == NDX_DONE) {
			if (am_sender)
				write_ndx(f_out, NDX_DONE);
			else {
				if (batch_gen_fd >= 0) {
					while (read_int(batch_gen_fd) != NDX_DEL_STATS) {}
					read_del_stats(batch_gen_fd);
				}
				write_int(f_out, NDX_DONE);
			}
			i = read_ndx_and_attrs(f_in, f_out, &iflags, &fnamecmp_type, xname, &xlen);
		}
	}

	if (i != NDX_DONE) {
		rprintf(FERROR, "Invalid packet at end of run (%d) [%s]\n",
			i, who_am_i());
		exit_cleanup(RERR_PROTOCOL);
	}
}

static void do_server_sender(int f_in, int f_out, int argc, char *argv[])
{
	struct file_list *flist;
	char *dir;

	if (DEBUG_GTE(SEND, 1))
		rprintf(FINFO, "server_sender starting pid=%d\n", (int)getpid());

	if (am_daemon && lp_write_only(module_id)) {
		rprintf(FERROR, "ERROR: module is write only\n");
		exit_cleanup(RERR_SYNTAX);
	}
	if (am_daemon && read_only && remove_source_files) {
		rprintf(FERROR,
			"ERROR: --remove-%s-files cannot be used with a read-only module\n",
			remove_source_files == 1 ? "source" : "sent");
		exit_cleanup(RERR_SYNTAX);
	}
	if (argc < 1) {
		rprintf(FERROR, "ERROR: do_server_sender called without args\n");
		exit_cleanup(RERR_SYNTAX);
	}

	become_copy_as_user();

	dir = argv[0];
	if (!relative_paths) {
		if (!change_dir(dir, CD_NORMAL)) {
			rsyserr(FERROR, errno, "change_dir#3 %s failed",
				full_fname(dir));
			exit_cleanup(RERR_FILESELECT);
		}
	}
	argc--;
	argv++;

	if (argc == 0 && (recurse || xfer_dirs || list_only)) {
		argc = 1;
		argv--;
		argv[0] = ".";
	}

	flist = send_file_list(f_out,argc,argv);
	if (!flist || flist->used == 0) {
		/* Make sure input buffering is off so we can't hang in noop_io_until_death(). */
		io_end_buffering_in(0);
		/* TODO:  we should really exit in a more controlled manner. */
		exit_cleanup(0);
	}

	io_start_buffering_in(f_in);

	send_files(f_in, f_out);
	io_flush(FULL_FLUSH);
	handle_stats(f_out);
	if (protocol_version >= 24)
		read_final_goodbye(f_in, f_out);
	io_flush(FULL_FLUSH);
	exit_cleanup(0);
}


static int do_recv(int f_in, int f_out, char *local_name)
{
	int pid;
	int exit_code = 0;
	int error_pipe[2];

	/* The receiving side mustn't obey this, or an existing symlink that
	 * points to an identical file won't be replaced by the referent. */
	copy_links = copy_dirlinks = copy_unsafe_links = 0;

#ifdef SUPPORT_HARD_LINKS
	if (preserve_hard_links && !inc_recurse)
		match_hard_links(first_flist);
#endif

	if (fd_pair(error_pipe) < 0) {
		rsyserr(FERROR, errno, "pipe failed in do_recv");
		exit_cleanup(RERR_IPC);
	}

	if (backup_dir) {
		STRUCT_STAT st;
		int ret;
		if (backup_dir_len > 1)
			backup_dir_buf[backup_dir_len-1] = '\0';
		ret = do_stat(backup_dir_buf, &st);
		if (ret != 0 || !S_ISDIR(st.st_mode)) {
			if (ret == 0) {
				rprintf(FERROR, "The backup-dir is not a directory: %s\n", backup_dir_buf);
				exit_cleanup(RERR_SYNTAX);
			}
			if (errno != ENOENT) {
				rprintf(FERROR, "Failed to stat %s: %s\n", backup_dir_buf, strerror(errno));
				exit_cleanup(RERR_FILEIO);
			}
			if (INFO_GTE(BACKUP, 1))
				rprintf(FINFO, "(new) backup_dir is %s\n", backup_dir_buf);
		} else if (INFO_GTE(BACKUP, 1))
			rprintf(FINFO, "backup_dir is %s\n", backup_dir_buf);
		if (backup_dir_len > 1)
			backup_dir_buf[backup_dir_len-1] = '/';
	}

	if (tmpdir) {
		STRUCT_STAT st;
		int ret = do_stat(tmpdir, &st);
		if (ret < 0 || !S_ISDIR(st.st_mode)) {
			if (ret == 0) {
				rprintf(FERROR, "The temp-dir is not a directory: %s\n", tmpdir);
				exit_cleanup(RERR_SYNTAX);
			}
			if (errno == ENOENT) {
				rprintf(FERROR, "The temp-dir does not exist: %s\n", tmpdir);
				exit_cleanup(RERR_SYNTAX);
			}
			rprintf(FERROR, "Failed to stat temp-dir %s: %s\n", tmpdir, strerror(errno));
			exit_cleanup(RERR_FILEIO);
		}
	}

	io_flush(FULL_FLUSH);

	if ((pid = do_fork()) == -1) {
		rsyserr(FERROR, errno, "fork failed in do_recv");
		exit_cleanup(RERR_IPC);
	}

	if (pid == 0) {
		am_receiver = 1;
		send_msgs_to_gen = am_server;

		close(error_pipe[0]);

		/* We can't let two processes write to the socket at one time. */
		io_end_multiplex_out(MPLX_SWITCHING);
		if (f_in != f_out)
			close(f_out);
		sock_f_out = -1;
		f_out = error_pipe[1];

		bwlimit_writemax = 0; /* receiver doesn't need to do this */

		if (read_batch)
			io_start_buffering_in(f_in);
		io_start_multiplex_out(f_out);

		recv_files(f_in, f_out, local_name);
		io_flush(FULL_FLUSH);
		handle_stats(f_in);

		if (output_needs_newline) {
			fputc('\n', stdout);
			output_needs_newline = 0;
		}

		write_int(f_out, NDX_DONE);
		send_msg(MSG_STATS, (char*)&stats.total_read, sizeof stats.total_read, 0);
		io_flush(FULL_FLUSH);

		/* Handle any keep-alive packets from the post-processing work
		 * that the generator does. */
		if (protocol_version >= 29) {
			kluge_around_eof = -1;

			/* This should only get stopped via a USR2 signal. */
			read_final_goodbye(f_in, f_out);

			rprintf(FERROR, "Invalid packet at end of run [%s]\n",
				who_am_i());
			exit_cleanup(RERR_PROTOCOL);
		}

		/* Finally, we go to sleep until our parent kills us with a
		 * USR2 signal.  We sleep for a short time, as on some OSes
		 * a signal won't interrupt a sleep! */
		while (1)
			msleep(20);
	}

	am_generator = 1;
	implied_filter_list.head = implied_filter_list.tail = NULL;
	flist_receiving_enabled = True;

	io_end_multiplex_in(MPLX_SWITCHING);
	if (write_batch && !am_server)
		stop_write_batch();

	close(error_pipe[1]);
	if (f_in != f_out)
		close(f_in);
	sock_f_in = -1;
	f_in = error_pipe[0];

	io_start_buffering_out(f_out);
	io_start_multiplex_in(f_in);

#ifdef SUPPORT_HARD_LINKS
	if (preserve_hard_links && inc_recurse) {
		struct file_list *flist;
		for (flist = first_flist; flist; flist = flist->next)
			match_hard_links(flist);
	}
#endif

	generate_files(f_out, local_name);

	handle_stats(-1);
	io_flush(FULL_FLUSH);
	shutting_down = True;
	if (protocol_version >= 24) {
		/* send a final goodbye message */
		write_ndx(f_out, NDX_DONE);
	}
	io_flush(FULL_FLUSH);

	kill(pid, SIGUSR2);
	wait_process_with_flush(pid, &exit_code);
	return exit_code;
}

static void do_server_recv(int f_in, int f_out, int argc, char *argv[])
{
	int exit_code;
	struct file_list *flist;
	char *local_name = NULL;
	int negated_levels;

	if (filesfrom_fd >= 0 && msgs2stderr != 1 && protocol_version < 31) {
		/* We can't mix messages with files-from data on the socket,
		 * so temporarily turn off info/debug messages. */
		negate_output_levels();
		negated_levels = 1;
	} else
		negated_levels = 0;

	if (DEBUG_GTE(RECV, 1))
		rprintf(FINFO, "server_recv(%d) starting pid=%d\n", argc, (int)getpid());

	if (am_daemon && read_only) {
		rprintf(FERROR,"ERROR: module is read only\n");
		exit_cleanup(RERR_SYNTAX);
		return;
	}

	become_copy_as_user();

	if (argc > 0) {
		char *dir = argv[0];
		argc--;
		argv++;
		if (!am_daemon && !change_dir(dir, CD_NORMAL)) {
			rsyserr(FERROR, errno, "change_dir#4 %s failed",
				full_fname(dir));
			exit_cleanup(RERR_FILESELECT);
		}
	}

	if (protocol_version >= 30)
		io_start_multiplex_in(f_in);
	else
		io_start_buffering_in(f_in);
	recv_filter_list(f_in);

	if (filesfrom_fd >= 0) {
		/* We need to send the files-from names to the sender at the
		 * same time that we receive the file-list from them, so we
		 * need the IO routines to automatically write out the names
		 * onto our f_out socket as we read the file-list.  This
		 * avoids both deadlock and extra delays/buffers. */
		start_filesfrom_forwarding(filesfrom_fd);
		filesfrom_fd = -1;
	}

	flist = recv_file_list(f_in, -1);
	if (!flist) {
		rprintf(FERROR,"server_recv: recv_file_list error\n");
		exit_cleanup(RERR_FILESELECT);
	}
	if (inc_recurse && file_total == 1)
		recv_additional_file_list(f_in);

	if (negated_levels)
		negate_output_levels();

	if (argc > 0)
		local_name = get_local_name(flist,argv[0]);

	/* Now that we know what our destination directory turned out to be,
	 * we can sanitize the --link-/copy-/compare-dest args correctly. */
	if (sanitize_paths) {
		char **dir_p;
		for (dir_p = basis_dir; *dir_p; dir_p++)
			*dir_p = sanitize_path(NULL, *dir_p, NULL, curr_dir_depth, SP_DEFAULT);
		if (partial_dir)
			partial_dir = sanitize_path(NULL, partial_dir, NULL, curr_dir_depth, SP_DEFAULT);
	}
	check_alt_basis_dirs();

	if (daemon_filter_list.head) {
		char **dir_p;
		filter_rule_list *elp = &daemon_filter_list;

		for (dir_p = basis_dir; *dir_p; dir_p++) {
			char *dir = *dir_p;
			if (*dir == '/')
				dir += module_dirlen;
			if (check_filter(elp, FLOG, dir, 1) < 0)
				goto options_rejected;
		}
		if (partial_dir && *partial_dir == '/'
		 && check_filter(elp, FLOG, partial_dir + module_dirlen, 1) < 0) {
		    options_rejected:
			rprintf(FERROR, "Your options have been rejected by the server.\n");
			exit_cleanup(RERR_SYNTAX);
		}
	}

	exit_code = do_recv(f_in, f_out, local_name);
	exit_cleanup(exit_code);
}


int child_main(int argc, char *argv[])
{
	start_server(STDIN_FILENO, STDOUT_FILENO, argc, argv);
	return 0;
}


void start_server(int f_in, int f_out, int argc, char *argv[])
{
	set_nonblocking(f_in);
	set_nonblocking(f_out);

	io_set_sock_fds(f_in, f_out);
	setup_protocol(f_out, f_in);

	if (protocol_version >= 23)
		io_start_multiplex_out(f_out);
	if (am_daemon && io_timeout && protocol_version >= 31)
		send_msg_int(MSG_IO_TIMEOUT, io_timeout);

	if (am_sender) {
		keep_dirlinks = 0; /* Must be disabled on the sender. */
		if (need_messages_from_generator)
			io_start_multiplex_in(f_in);
		else
			io_start_buffering_in(f_in);
		recv_filter_list(f_in);
		do_server_sender(f_in, f_out, argc, argv);
	} else
		do_server_recv(f_in, f_out, argc, argv);
	exit_cleanup(0);
}

/* This is called once the connection has been negotiated.  It is used
 * for rsyncd, remote-shell, and local connections. */
int client_run(int f_in, int f_out, pid_t pid, int argc, char *argv[])
{
	struct file_list *flist = NULL;
	int exit_code = 0, exit_code2 = 0;
	char *local_name = NULL;

	cleanup_child_pid = pid;
	if (!read_batch) {
		set_nonblocking(f_in);
		set_nonblocking(f_out);
	}

	io_set_sock_fds(f_in, f_out);
	setup_protocol(f_out,f_in);

	/* We set our stderr file handle to blocking because ssh might have
	 * set it to non-blocking.  This can be particularly troublesome if
	 * stderr is a clone of stdout, because ssh would have set our stdout
	 * to non-blocking at the same time (which can easily cause us to lose
	 * output from our print statements).  This kluge shouldn't cause ssh
	 * any problems for how we use it.  Note also that we delayed setting
	 * this until after the above protocol setup so that we know for sure
	 * that ssh is done twiddling its file descriptors.  */
	set_blocking(STDERR_FILENO);

	if (am_sender) {
		keep_dirlinks = 0; /* Must be disabled on the sender. */

		if (always_checksum
		 && (log_format_has(stdout_format, 'C')
		  || log_format_has(logfile_format, 'C')))
			sender_keeps_checksum = 1;

		if (protocol_version >= 30)
			io_start_multiplex_out(f_out);
		else
			io_start_buffering_out(f_out);
		if (protocol_version >= 31 || (!filesfrom_host && protocol_version >= 23))
			io_start_multiplex_in(f_in);
		else
			io_start_buffering_in(f_in);
		send_filter_list(f_out);
		if (filesfrom_host)
			filesfrom_fd = f_in;

		if (write_batch && !am_server)
			start_write_batch(f_out);

		become_copy_as_user();

		flist = send_file_list(f_out, argc, argv);
		if (DEBUG_GTE(FLIST, 3))
			rprintf(FINFO,"file list sent\n");

		if (protocol_version < 31 && filesfrom_host && protocol_version >= 23)
			io_start_multiplex_in(f_in);

		io_flush(NORMAL_FLUSH);
		send_files(f_in, f_out);
		io_flush(FULL_FLUSH);
		handle_stats(-1);
		if (protocol_version >= 24)
			read_final_goodbye(f_in, f_out);
		if (pid != -1) {
			if (DEBUG_GTE(EXIT, 2))
				rprintf(FINFO,"client_run waiting on %d\n", (int) pid);
			io_flush(FULL_FLUSH);
			wait_process_with_flush(pid, &exit_code);
		}
		output_summary();
		io_flush(FULL_FLUSH);
		exit_cleanup(exit_code);
	}

	if (!read_batch) {
		if (protocol_version >= 23)
			io_start_multiplex_in(f_in);
		if (need_messages_from_generator)
			io_start_multiplex_out(f_out);
		else
			io_start_buffering_out(f_out);
	}

	become_copy_as_user();

	send_filter_list(read_batch ? -1 : f_out);

	if (filesfrom_fd >= 0) {
		start_filesfrom_forwarding(filesfrom_fd);
		filesfrom_fd = -1;
	}

	if (write_batch && !am_server)
		start_write_batch(f_in);
	flist = recv_file_list(f_in, -1);
	if (inc_recurse && file_total == 1)
		recv_additional_file_list(f_in);

	if (flist && flist->used > 0) {
		local_name = get_local_name(flist, argv[0]);

		check_alt_basis_dirs();

		exit_code2 = do_recv(f_in, f_out, local_name);
	} else {
		handle_stats(-1);
		output_summary();
	}

	if (pid != -1) {
		if (DEBUG_GTE(RECV, 1))
			rprintf(FINFO,"client_run2 waiting on %d\n", (int) pid);
		io_flush(FULL_FLUSH);
		wait_process_with_flush(pid, &exit_code);
	}

	return MAX(exit_code, exit_code2);
}

/* Start a client for either type of remote connection.  Work out
 * whether the arguments request a remote shell or rsyncd connection,
 * and call the appropriate connection function, then run_client.
 *
 * Calls either start_socket_client (for sockets) or do_cmd and
 * client_run (for ssh). */
static int start_client(int argc, char *argv[])
{
	char *p, *shell_machine = NULL, *shell_user = NULL;
	char **remote_argv;
	int remote_argc, env_port = rsync_port;
	int f_in, f_out;
	int ret;
	pid_t pid;

	if (!read_batch) { /* for read_batch, NO source is specified */
		char *path = check_for_hostspec(argv[0], &shell_machine, &rsync_port);
		if (path) { /* source is remote */
			char *dummy_host;
			int dummy_port = 0;
			*argv = path;
			remote_argv = argv;
			remote_argc = argc;
			argv += argc - 1;
			if (argc == 1 || **argv == ':')
				argc = 0; /* no dest arg */
			else if (check_for_hostspec(*argv, &dummy_host, &dummy_port)) {
				rprintf(FERROR,
					"The source and destination cannot both be remote.\n");
				exit_cleanup(RERR_SYNTAX);
			} else {
				remote_argc--; /* don't count dest */
				argc = 1;
			}
			if (filesfrom_host && *filesfrom_host && strcmp(filesfrom_host, shell_machine) != 0) {
				rprintf(FERROR,
					"--files-from hostname is not the same as the transfer hostname\n");
				exit_cleanup(RERR_SYNTAX);
			}
			am_sender = 0;
			if (rsync_port)
				daemon_connection = shell_cmd ? 1 : -1;
		} else { /* source is local, check dest arg */
			am_sender = 1;

			if (argc > 1) {
				p = argv[--argc];
				if (!*p)
					p = dot_dir_or_error();
				remote_argv = argv + argc;
			} else {
				static char *dotarg[1] = { "." };
				p = dotarg[0];
				remote_argv = dotarg;
			}
			remote_argc = 1;

			path = check_for_hostspec(p, &shell_machine, &rsync_port);
			if (path && filesfrom_host && *filesfrom_host && strcmp(filesfrom_host, shell_machine) != 0) {
				rprintf(FERROR,
					"--files-from hostname is not the same as the transfer hostname\n");
				exit_cleanup(RERR_SYNTAX);
			}
			if (!path) { /* no hostspec found, so src & dest are local */
				local_server = 1;
				if (filesfrom_host) {
					rprintf(FERROR,
						"--files-from cannot be remote when the transfer is local\n");
					exit_cleanup(RERR_SYNTAX);
				}
				shell_machine = NULL;
				rsync_port = 0;
			} else { /* hostspec was found, so dest is remote */
				argv[argc] = path;
				if (rsync_port)
					daemon_connection = shell_cmd ? 1 : -1;
			}
		}
	} else {  /* read_batch */
		local_server = 1;
		if (check_for_hostspec(argv[argc-1], &shell_machine, &rsync_port)) {
			rprintf(FERROR, "remote destination is not allowed with --read-batch\n");
			exit_cleanup(RERR_SYNTAX);
		}
		remote_argv = argv += argc - 1;
		remote_argc = argc = 1;
		rsync_port = 0;
	}

	/* A local transfer doesn't unbackslash anything, so leave the args alone. */
	if (local_server) {
		old_style_args = 2;
		trust_sender_args = trust_sender_filter = 1;
	}

	if (!rsync_port && remote_argc && !**remote_argv) /* Turn an empty arg into a dot dir. */
		*remote_argv = ".";

	if (am_sender) {
		char *dummy_host;
		int dummy_port = rsync_port;
		int i;
		if (!argv[0][0])
			goto invalid_empty;
		/* For local source, extra source args must not have hostspec. */
		for (i = 1; i < argc; i++) {
			if (!argv[i][0]) {
			    invalid_empty:
				rprintf(FERROR, "Empty source arg specified.\n");
				exit_cleanup(RERR_SYNTAX);
			}
			if (check_for_hostspec(argv[i], &dummy_host, &dummy_port)) {
				rprintf(FERROR, "Unexpected remote arg: %s\n", argv[i]);
				exit_cleanup(RERR_SYNTAX);
			}
		}
	} else {
		char *dummy_host;
		int dummy_port = rsync_port;
		int i;
		if (filesfrom_fd < 0)
			add_implied_include(remote_argv[0], daemon_connection);
		/* For remote source, any extra source args must have either
		 * the same hostname or an empty hostname. */
		for (i = 1; i < remote_argc; i++) {
			char *arg = check_for_hostspec(remote_argv[i], &dummy_host, &dummy_port);
			if (!arg) {
				rprintf(FERROR, "Unexpected local arg: %s\n", remote_argv[i]);
				rprintf(FERROR, "If arg is a remote file/dir, prefix it with a colon (:).\n");
				exit_cleanup(RERR_SYNTAX);
			}
			if (*dummy_host && strcmp(dummy_host, shell_machine) != 0) {
				rprintf(FERROR, "All source args must come from the same machine.\n");
				exit_cleanup(RERR_SYNTAX);
			}
			if (rsync_port != dummy_port) {
				if (!rsync_port || !dummy_port)
					rprintf(FERROR, "All source args must use the same hostspec format.\n");
				else
					rprintf(FERROR, "All source args must use the same port number.\n");
				exit_cleanup(RERR_SYNTAX);
			}
			if (!rsync_port && !*arg) /* Turn an empty arg into a dot dir. */
				arg = ".";
			remote_argv[i] = arg;
			add_implied_include(arg, daemon_connection);
		}
	}

	if (rsync_port < 0)
		rsync_port = RSYNC_PORT;
	else
		env_port = rsync_port;

	if (daemon_connection < 0)
		return start_socket_client(shell_machine, remote_argc, remote_argv, argc, argv);

	if (password_file && !daemon_connection) {
		rprintf(FERROR, "The --password-file option may only be "
				"used when accessing an rsync daemon.\n");
		exit_cleanup(RERR_SYNTAX);
	}

	if (connect_timeout) {
		rprintf(FERROR, "The --contimeout option may only be "
				"used when connecting to an rsync daemon.\n");
		exit_cleanup(RERR_SYNTAX);
	}

	if (shell_machine) {
		p = strrchr(shell_machine,'@');
		if (p) {
			*p = 0;
			shell_user = shell_machine;
			shell_machine = p+1;
		}
	}

	if (DEBUG_GTE(CMD, 2)) {
		rprintf(FINFO,"cmd=%s machine=%s user=%s path=%s\n",
			NS(shell_cmd), NS(shell_machine), NS(shell_user),
			NS(remote_argv[0]));
	}

#ifdef HAVE_PUTENV
	if (daemon_connection)
		set_env_num("RSYNC_PORT", env_port);
#else
	(void)env_port;
#endif

	pid = do_cmd(shell_cmd, shell_machine, shell_user, remote_argv, remote_argc, &f_in, &f_out);

	/* if we're running an rsync server on the remote host over a
	 * remote shell command, we need to do the RSYNCD protocol first */
	if (daemon_connection) {
		int tmpret;
		tmpret = start_inband_exchange(f_in, f_out, shell_user, remote_argc, remote_argv);
		if (tmpret < 0)
			return tmpret;
	}

	ret = client_run(f_in, f_out, pid, argc, argv);

	fflush(stdout);
	fflush(stderr);

	return ret;
}


static void sigusr1_handler(UNUSED(int val))
{
	called_from_signal_handler = 1;
	exit_cleanup(RERR_SIGNAL1);
}

static void sigusr2_handler(UNUSED(int val))
{
	if (!am_server)
		output_summary();
	close_all();
	if (got_xfer_error)
		_exit(RERR_PARTIAL);
	_exit(0);
}

#if defined SIGINFO || defined SIGVTALRM
static void siginfo_handler(UNUSED(int val))
{
	if (!am_server && !INFO_GTE(PROGRESS, 1))
		want_progress_now = True;
}
#endif

void remember_children(UNUSED(int val))
{
#ifdef WNOHANG
	int cnt, status;
	pid_t pid;
	/* An empty waitpid() loop was put here by Tridge and we could never
	 * get him to explain why he put it in, so rather than taking it
	 * out we're instead saving the child exit statuses for later use.
	 * The waitpid() loop presumably eliminates all possibility of leaving
	 * zombie children, maybe that's why he did it. */
	while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
		/* save the child's exit status */
		for (cnt = 0; cnt < MAXCHILDPROCS; cnt++) {
			if (pid_stat_table[cnt].pid == 0) {
				pid_stat_table[cnt].pid = pid;
				pid_stat_table[cnt].status = status;
				break;
			}
		}
	}
#endif
#ifndef HAVE_SIGACTION
	signal(SIGCHLD, remember_children);
#endif
}

/**
 * This routine catches signals and tries to send them to gdb.
 *
 * Because it's called from inside a signal handler it ought not to
 * use too many library routines.
 *
 * @todo Perhaps use "screen -X" instead/as well, to help people
 * debugging without easy access to X.  Perhaps use an environment
 * variable, or just call a script?
 *
 * @todo The /proc/ magic probably only works on Linux (and
 * Solaris?)  Can we be more portable?
 **/
#ifdef MAINTAINER_MODE
const char *get_panic_action(void)
{
	const char *cmd_fmt = getenv("RSYNC_PANIC_ACTION");

	if (cmd_fmt)
		return cmd_fmt;
	return "xterm -display :0 -T Panic -n Panic -e gdb /proc/%d/exe %d";
}

/**
 * Handle a fatal signal by launching a debugger, controlled by $RSYNC_PANIC_ACTION.
 *
 * This signal handler is only installed if we were configured with
 * --enable-maintainer-mode.  Perhaps it should always be on and we
 * should just look at the environment variable, but I'm a bit leery
 * of a signal sending us into a busy loop.
 **/
static void rsync_panic_handler(UNUSED(int whatsig))
{
	char cmd_buf[300];
	int ret, pid_int = getpid();

	snprintf(cmd_buf, sizeof cmd_buf, get_panic_action(), pid_int, pid_int);

	/* Unless we failed to execute gdb, we allow the process to
	 * continue.  I'm not sure if that's right. */
	ret = shell_exec(cmd_buf);
	if (ret)
		_exit(ret);
}
#endif

static void unset_env_var(const char *var)
{
#ifdef HAVE_UNSETENV
	unsetenv(var);
#else
#ifdef HAVE_PUTENV
	char *mem;
	if (asprintf(&mem, "%s=", var) < 0)
		out_of_memory("unset_env_var");
	putenv(mem);
#else
	(void)var;
#endif
#endif
}


int main(int argc,char *argv[])
{
	int ret;

	raw_argc = argc;
	raw_argv = argv;

#ifdef HAVE_SIGACTION
# ifdef HAVE_SIGPROCMASK
	sigset_t sigmask;

	sigemptyset(&sigmask);
# endif
	sigact.sa_flags = SA_NOCLDSTOP;
#endif
	SIGACTMASK(SIGUSR1, sigusr1_handler);
	SIGACTMASK(SIGUSR2, sigusr2_handler);
	SIGACTMASK(SIGCHLD, remember_children);
#ifdef MAINTAINER_MODE
	SIGACTMASK(SIGSEGV, rsync_panic_handler);
	SIGACTMASK(SIGFPE, rsync_panic_handler);
	SIGACTMASK(SIGABRT, rsync_panic_handler);
	SIGACTMASK(SIGBUS, rsync_panic_handler);
#endif
#ifdef SIGINFO
	SIGACTMASK(SIGINFO, siginfo_handler);
#endif
#ifdef SIGVTALRM
	SIGACTMASK(SIGVTALRM, siginfo_handler);
#endif

	starttime = time(NULL);
	our_uid = MY_UID();
	our_gid = MY_GID();
	am_root = our_uid == ROOT_UID;

	unset_env_var("DISPLAY");

#if defined USE_OPENSSL && defined SET_OPENSSL_CONF
#define TO_STR2(x) #x
#define TO_STR(x) TO_STR2(x)
	/* ./configure --with-openssl-conf=/etc/ssl/openssl-rsync.cnf
	 * defines SET_OPENSSL_CONF as that unquoted pathname. */
	if (!getenv("OPENSSL_CONF")) /* Don't override it if it's already set. */
		set_env_str("OPENSSL_CONF", TO_STR(SET_OPENSSL_CONF));
#undef TO_STR
#undef TO_STR2
#endif

	memset(&stats, 0, sizeof(stats));

	/* Even a non-daemon runs needs the default config values to be set, e.g.
	 * lp_dont_compress() is queried when no --skip-compress option is set. */
	reset_daemon_vars();

	if (argc < 2) {
		usage(FERROR);
		exit_cleanup(RERR_SYNTAX);
	}

	/* Get the umask for use in permission calculations.  We no longer set
	 * it to zero; that is ugly and pointless now that all the callers that
	 * relied on it have been reeducated to work with default ACLs. */
	umask(orig_umask = umask(0));

#if defined CONFIG_LOCALE && defined HAVE_SETLOCALE
	setlocale(LC_CTYPE, "");
	setlocale(LC_NUMERIC, "");
#endif

	if (!parse_arguments(&argc, (const char ***) &argv)) {
		option_error();
		exit_cleanup(RERR_SYNTAX);
	}
	if (write_batch
	 && poptDupArgv(argc, (const char **)argv, &cooked_argc, (const char ***)&cooked_argv) != 0)
		out_of_memory("main");

	SIGACTMASK(SIGINT, sig_int);
	SIGACTMASK(SIGHUP, sig_int);
	SIGACTMASK(SIGTERM, sig_int);
#if defined HAVE_SIGACTION && HAVE_SIGPROCMASK
	sigprocmask(SIG_UNBLOCK, &sigmask, NULL);
#endif

	/* Ignore SIGPIPE; we consistently check error codes and will
	 * see the EPIPE. */
	SIGACTION(SIGPIPE, SIG_IGN);
#ifdef SIGXFSZ
	SIGACTION(SIGXFSZ, SIG_IGN);
#endif

	/* Initialize change_dir() here because on some old systems getcwd
	 * (implemented by forking "pwd" and reading its output) doesn't
	 * work when there are other child processes.  Also, on all systems
	 * that implement getcwd that way "pwd" can't be found after chroot. */
	change_dir(NULL, CD_NORMAL);

	if ((write_batch || read_batch) && !am_server) {
		open_batch_files(); /* sets batch_fd */
		if (read_batch)
			read_stream_flags(batch_fd);
		else
			write_stream_flags(batch_fd);
	}
	if (write_batch < 0)
		dry_run = 1;

	if (am_server) {
#ifdef ICONV_CONST
		setup_iconv();
#endif
	} else if (am_daemon)
		return daemon_main();

	if (am_server && protect_args) {
		char buf[MAXPATHLEN];
		protect_args = 2;
		read_args(STDIN_FILENO, NULL, buf, sizeof buf, 1, &argv, &argc, NULL);
		if (!parse_arguments(&argc, (const char ***) &argv)) {
			option_error();
			exit_cleanup(RERR_SYNTAX);
		}
	}

	if (argc < 1) {
		usage(FERROR);
		exit_cleanup(RERR_SYNTAX);
	}

	if (am_server) {
		set_nonblocking(STDIN_FILENO);
		set_nonblocking(STDOUT_FILENO);
		if (am_daemon)
			return start_daemon(STDIN_FILENO, STDOUT_FILENO);
		start_server(STDIN_FILENO, STDOUT_FILENO, argc, argv);
	}

	ret = start_client(argc, argv);
	if (ret == -1)
		exit_cleanup(RERR_STARTCLIENT);
	else
		exit_cleanup(ret);

	return ret;
}
/*
 * Block matching used by the file-transfer code.
 *
 * Copyright (C) 1996 Andrew Tridgell
 * Copyright (C) 1996 Paul Mackerras
 * Copyright (C) 2003-2023 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"
#include "inums.h"

extern int checksum_seed;
extern int append_mode;

extern struct name_num_item *xfer_sum_nni;
extern int xfer_sum_len;

int updating_basis_file;
char sender_file_sum[MAX_DIGEST_LEN];

static int false_alarms;
static int hash_hits;
static int matches;
static int64 data_transfer;

static int total_false_alarms;
static int total_hash_hits;
static int total_matches;

extern struct stats stats;

#define TRADITIONAL_TABLESIZE (1<<16)

static uint32 tablesize;
static int32 *hash_table;

#define SUM2HASH2(s1,s2) (((s1) + (s2)) & 0xFFFF)
#define SUM2HASH(sum) SUM2HASH2((sum)&0xFFFF,(sum)>>16)

#define BIG_SUM2HASH(sum) ((sum)%tablesize)

static void build_hash_table(struct sum_struct *s)
{
	static uint32 alloc_size;
	int32 i;

	/* Dynamically calculate the hash table size so that the hash load
	 * for big files is about 80%.  A number greater than the traditional
	 * size must be odd or s2 will not be able to span the entire set. */
	tablesize = (uint32)(s->count/8) * 10 + 11;
	if (tablesize < TRADITIONAL_TABLESIZE)
		tablesize = TRADITIONAL_TABLESIZE;
	if (tablesize > alloc_size || tablesize < alloc_size - 16*1024) {
		if (hash_table)
			free(hash_table);
		hash_table = new_array(int32, tablesize);
		alloc_size = tablesize;
	}

	memset(hash_table, 0xFF, tablesize * sizeof hash_table[0]);

	if (tablesize == TRADITIONAL_TABLESIZE) {
		for (i = 0; i < s->count; i++) {
			uint32 t = SUM2HASH(s->sums[i].sum1);
			s->sums[i].chain = hash_table[t];
			hash_table[t] = i;
		}
	} else {
		for (i = 0; i < s->count; i++) {
			uint32 t = BIG_SUM2HASH(s->sums[i].sum1);
			s->sums[i].chain = hash_table[t];
			hash_table[t] = i;
		}
	}
}


static OFF_T last_match;


/* Transmit a literal and/or match token.
 *
 * This delightfully-named function is called either when we find a
 * match and need to transmit all the unmatched data leading up to it,
 * or when we get bored of accumulating literal data and just need to
 * transmit it.  As a result of this second case, it is called even if
 * we have not matched at all!
 *
 * If i >= 0, the number of a matched token.  If < 0, indicates we have
 * only literal data.  A -1 will send a 0-token-int too, and a -2 sends
 * only literal data, w/o any token-int. */
static void matched(int f, struct sum_struct *s, struct map_struct *buf, OFF_T offset, int32 i)
{
	int32 n = (int32)(offset - last_match); /* max value: block_size (int32) */
	int32 j;

	if (DEBUG_GTE(DELTASUM, 2) && i >= 0) {
		rprintf(FINFO,
			"match at %s last_match=%s j=%d len=%ld n=%ld\n",
			big_num(offset), big_num(last_match), i,
			(long)s->sums[i].len, (long)n);
	}

	send_token(f, i, buf, last_match, n, i < 0 ? 0 : s->sums[i].len);
	data_transfer += n;

	if (i >= 0) {
		stats.matched_data += s->sums[i].len;
		n += s->sums[i].len;
	}

	for (j = 0; j < n; j += CHUNK_SIZE) {
		int32 n1 = MIN(CHUNK_SIZE, n - j);
		sum_update(map_ptr(buf, last_match + j, n1), n1);
	}

	if (i >= 0)
		last_match = offset + s->sums[i].len;
	else
		last_match = offset;

	if (buf && INFO_GTE(PROGRESS, 1))
		show_progress(last_match, buf->file_size);
}


static void hash_search(int f,struct sum_struct *s,
			struct map_struct *buf, OFF_T len)
{
	OFF_T offset, aligned_offset, end;
	int32 k, want_i, aligned_i, backup;
	char sum2[MAX_DIGEST_LEN];
	uint32 s1, s2, sum;
	int more;
	schar *map;

	// prevent possible memory leaks
	memset(sum2, 0, sizeof sum2);

	/* want_i is used to encourage adjacent matches, allowing the RLL
	 * coding of the output to work more efficiently. */
	want_i = 0;

	if (DEBUG_GTE(DELTASUM, 2)) {
		rprintf(FINFO, "hash search b=%ld len=%s\n",
			(long)s->blength, big_num(len));
	}

	k = (int32)MIN(len, (OFF_T)s->blength);

	map = (schar *)map_ptr(buf, 0, k);

	sum = get_checksum1((char *)map, k);
	s1 = sum & 0xFFFF;
	s2 = sum >> 16;
	if (DEBUG_GTE(DELTASUM, 3))
		rprintf(FINFO, "sum=%.8x k=%ld\n", sum, (long)k);

	offset = aligned_offset = aligned_i = 0;

	end = len + 1 - s->sums[s->count-1].len;

	if (DEBUG_GTE(DELTASUM, 3)) {
		rprintf(FINFO, "hash search s->blength=%ld len=%s count=%s\n",
			(long)s->blength, big_num(len), big_num(s->count));
	}

	do {
		int done_csum2 = 0;
		uint32 hash_entry;
		int32 i, *prev;

		if (DEBUG_GTE(DELTASUM, 4)) {
			rprintf(FINFO, "offset=%s sum=%04x%04x\n",
				big_num(offset), s2 & 0xFFFF, s1 & 0xFFFF);
		}

		if (tablesize == TRADITIONAL_TABLESIZE) {
			hash_entry = SUM2HASH2(s1,s2);
			if ((i = hash_table[hash_entry]) < 0)
				goto null_hash;
			sum = (s1 & 0xffff) | (s2 << 16);
		} else {
			sum = (s1 & 0xffff) | (s2 << 16);
			hash_entry = BIG_SUM2HASH(sum);
			if ((i = hash_table[hash_entry]) < 0)
				goto null_hash;
		}
		prev = &hash_table[hash_entry];

		hash_hits++;
		do {
			int32 l;

			/* When updating in-place, the chunk's offset must be
			 * either >= our offset or identical data at that offset.
			 * Remove any bypassed entries that we can never use. */
			if (updating_basis_file && s->sums[i].offset < offset
			 && !(s->sums[i].flags & SUMFLG_SAME_OFFSET)) {
				*prev = s->sums[i].chain;
				continue;
			}
			prev = &s->sums[i].chain;

			if (sum != s->sums[i].sum1)
				continue;

			/* also make sure the two blocks are the same length */
			l = (int32)MIN((OFF_T)s->blength, len-offset);
			if (l != s->sums[i].len)
				continue;

			if (DEBUG_GTE(DELTASUM, 3)) {
				rprintf(FINFO,
					"potential match at %s i=%ld sum=%08x\n",
					big_num(offset), (long)i, sum);
			}

			if (!done_csum2) {
				map = (schar *)map_ptr(buf,offset,l);
				get_checksum2((char *)map,l,sum2);
				done_csum2 = 1;
			}

			if (memcmp(sum2, sum2_at(s, i), s->s2length) != 0) {
				false_alarms++;
				continue;
			}

			/* When updating in-place, the best possible match is
			 * one with an identical offset, so we prefer that over
			 * the adjacent want_i optimization. */
			if (updating_basis_file) {
				/* All the generator's chunks start at blength boundaries. */
				while (aligned_offset < offset) {
					aligned_offset += s->blength;
					aligned_i++;
				}
				if ((offset == aligned_offset
				  || (sum == 0 && l == s->blength && aligned_offset + l <= len))
				 && aligned_i < s->count) {
					if (i != aligned_i) {
						if (sum != s->sums[aligned_i].sum1
						 || l != s->sums[aligned_i].len
						 || memcmp(sum2, sum2_at(s, aligned_i), s->s2length) != 0)
							goto check_want_i;
						i = aligned_i;
					}
					if (offset != aligned_offset) {
						/* We've matched some zeros in a spot that is also zeros
						 * further along in the basis file, if we find zeros ahead
						 * in the sender's file, we'll output enough literal data
						 * to re-align with the basis file, and get back to seeking
						 * instead of writing. */
						backup = (int32)(aligned_offset - last_match);
						if (backup < 0)
							backup = 0;
						map = (schar *)map_ptr(buf, aligned_offset - backup, l + backup)
						    + backup;
						sum = get_checksum1((char *)map, l);
						if (sum != s->sums[i].sum1)
							goto check_want_i;
						get_checksum2((char *)map, l, sum2);
						if (memcmp(sum2, sum2_at(s, i), s->s2length) != 0)
							goto check_want_i;
						/* OK, we have a re-alignment match.  Bump the offset
						 * forward to the new match point. */
						offset = aligned_offset;
					}
					/* This identical chunk is in the same spot in the old and new file. */
					s->sums[i].flags |= SUMFLG_SAME_OFFSET;
					want_i = i;
				}
			}

		  check_want_i:
			/* we've found a match, but now check to see
			 * if want_i can hint at a better match. */
			if (i != want_i && want_i < s->count
			 && (!updating_basis_file || s->sums[want_i].offset >= offset
			  || s->sums[want_i].flags & SUMFLG_SAME_OFFSET)
			 && sum == s->sums[want_i].sum1
			 && memcmp(sum2, sum2_at(s, want_i), s->s2length) == 0) {
				/* we've found an adjacent match - the RLL coder
				 * will be happy */
				i = want_i;
			}
			want_i = i + 1;

			matched(f,s,buf,offset,i);
			offset += s->sums[i].len - 1;
			k = (int32)MIN((OFF_T)s->blength, len-offset);
			map = (schar *)map_ptr(buf, offset, k);
			sum = get_checksum1((char *)map, k);
			s1 = sum & 0xFFFF;
			s2 = sum >> 16;
			matches++;
			break;
		} while ((i = s->sums[i].chain) >= 0);

	  null_hash:
		backup = (int32)(offset - last_match);
		/* We sometimes read 1 byte prior to last_match... */
		if (backup < 0)
			backup = 0;

		/* Trim off the first byte from the checksum */
		more = offset + k < len;
		map = (schar *)map_ptr(buf, offset - backup, k + more + backup) + backup;
		s1 -= map[0] + CHAR_OFFSET;
		s2 -= k * (map[0]+CHAR_OFFSET);

		/* Add on the next byte (if there is one) to the checksum */
		if (more) {
			s1 += map[k] + CHAR_OFFSET;
			s2 += s1;
		} else
			--k;

		/* By matching early we avoid re-reading the
		   data 3 times in the case where a token
		   match comes a long way after last
		   match. The 3 reads are caused by the
		   running match, the checksum update and the
		   literal send. */
		if (backup >= s->blength+CHUNK_SIZE && end-offset > CHUNK_SIZE)
			matched(f, s, buf, offset - s->blength, -2);
	} while (++offset < end);

	matched(f, s, buf, len, -1);
	map_ptr(buf, len-1, 1);
}


/**
 * Scan through a origin file, looking for sections that match
 * checksums from the generator, and transmit either literal or token
 * data.
 *
 * Also calculates the MD4 checksum of the whole file, using the md
 * accumulator.  This is transmitted with the file as protection
 * against corruption on the wire.
 *
 * @param s Checksums received from the generator.  If <tt>s->count ==
 * 0</tt>, then there are actually no checksums for this file.
 *
 * @param len Length of the file to send.
 **/
void match_sums(int f, struct sum_struct *s, struct map_struct *buf, OFF_T len)
{
	last_match = 0;
	false_alarms = 0;
	hash_hits = 0;
	matches = 0;
	data_transfer = 0;

	sum_init(xfer_sum_nni, checksum_seed);

	if (append_mode > 0) {
		if (append_mode == 2) {
			OFF_T j = 0;
			for (j = CHUNK_SIZE; j < s->flength; j += CHUNK_SIZE) {
				if (buf && INFO_GTE(PROGRESS, 1))
					show_progress(last_match, buf->file_size);
				sum_update(map_ptr(buf, last_match, CHUNK_SIZE),
					   CHUNK_SIZE);
				last_match = j;
			}
			if (last_match < s->flength) {
				int32 n = (int32)(s->flength - last_match);
				if (buf && INFO_GTE(PROGRESS, 1))
					show_progress(last_match, buf->file_size);
				sum_update(map_ptr(buf, last_match, n), n);
			}
		}
		last_match = s->flength;
		s->count = 0;
	}

	if (len > 0 && s->count > 0) {
		build_hash_table(s);

		if (DEBUG_GTE(DELTASUM, 2))
			rprintf(FINFO,"built hash table\n");

		hash_search(f, s, buf, len);

		if (DEBUG_GTE(DELTASUM, 2))
			rprintf(FINFO,"done hash search\n");
	} else {
		OFF_T j;
		/* by doing this in pieces we avoid too many seeks */
		for (j = last_match + CHUNK_SIZE; j < len; j += CHUNK_SIZE)
			matched(f, s, buf, j, -2);
		matched(f, s, buf, len, -1);
	}

	sum_end(sender_file_sum);

	/* If we had a read error, send a bad checksum.  We use all bits
	 * off as long as the checksum doesn't happen to be that, in
	 * which case we turn the last 0 bit into a 1. */
	if (buf && buf->status != 0) {
		int i;
		for (i = 0; i < xfer_sum_len && sender_file_sum[i] == 0; i++) {}
		memset(sender_file_sum, 0, xfer_sum_len);
		if (i == xfer_sum_len)
			sender_file_sum[i-1]++;
	}

	if (DEBUG_GTE(DELTASUM, 2))
		rprintf(FINFO,"sending file_sum\n");
	write_buf(f, sender_file_sum, xfer_sum_len);

	if (DEBUG_GTE(DELTASUM, 2)) {
		rprintf(FINFO, "false_alarms=%d hash_hits=%d matches=%d\n",
			false_alarms, hash_hits, matches);
	}

	total_hash_hits += hash_hits;
	total_false_alarms += false_alarms;
	total_matches += matches;
	stats.literal_data += data_transfer;
}

void match_report(void)
{
	if (!DEBUG_GTE(DELTASUM, 1))
		return;

	rprintf(FINFO,
		"total: matches=%d  hash_hits=%d  false_alarms=%d data=%s\n",
		total_matches, total_hash_hits, total_false_alarms,
		big_num(stats.literal_data));
}
/*
 * Command-line (and received via daemon-socket) option parsing.
 *
 * Copyright (C) 1998-2001 Andrew Tridgell <tridge@samba.org>
 * Copyright (C) 2000, 2001, 2002 Martin Pool <mbp@samba.org>
 * Copyright (C) 2002-2023 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"
#include "itypes.h"
#include "ifuncs.h"
#include <popt.h>

extern int module_id;
extern int local_server;
extern int sanitize_paths;
extern int trust_sender_args;
extern int trust_sender_filter;
extern unsigned int module_dirlen;
extern filter_rule_list filter_list;
extern filter_rule_list daemon_filter_list;

int make_backups = 0;

/**
 * If 1, send the whole file as literal data rather than trying to
 * create an incremental diff.
 *
 * If -1, then look at whether we're local or remote and go by that.
 *
 * @sa disable_deltas_p()
 **/
int whole_file = -1;

int append_mode = 0;
int keep_dirlinks = 0;
int copy_dirlinks = 0;
int copy_links = 0;
int copy_devices = 0;
int write_devices = 0;
int preserve_links = 0;
int preserve_hard_links = 0;
int preserve_acls = 0;
int preserve_xattrs = 0;
int preserve_perms = 0;
int preserve_executability = 0;
int preserve_devices = 0;
int preserve_specials = 0;
int preserve_uid = 0;
int preserve_gid = 0;
int preserve_mtimes = 0;
int preserve_atimes = 0;
int preserve_crtimes = 0;
int omit_dir_times = 0;
int omit_link_times = 0;
int trust_sender = 0;
int update_only = 0;
int open_noatime = 0;
int cvs_exclude = 0;
int dry_run = 0;
int do_xfers = 1;
int do_fsync = 0;
int ignore_times = 0;
int delete_mode = 0;
int delete_during = 0;
int delete_before = 0;
int delete_after = 0;
int delete_excluded = 0;
int remove_source_files = 0;
int one_file_system = 0;
int protocol_version = PROTOCOL_VERSION;
int sparse_files = 0;
int preallocate_files = 0;
int do_compression = 0;
int do_compression_level = CLVL_NOT_SPECIFIED;
int am_root = 0; /* 0 = normal, 1 = root, 2 = --super, -1 = --fake-super */
int am_server = 0;
int am_sender = 0;
int am_starting_up = 1;
int relative_paths = -1;
int implied_dirs = 1;
int missing_args = 0; /* 0 = FERROR_XFER, 1 = ignore, 2 = delete */
int numeric_ids = 0;
int msgs2stderr = 2; /* Default: send errors to stderr for local & remote-shell transfers */
int saw_stderr_opt = 0;
int allow_8bit_chars = 0;
int force_delete = 0;
int io_timeout = 0;
int prune_empty_dirs = 0;
int use_qsort = 0;
char *files_from = NULL;
int filesfrom_fd = -1;
char *filesfrom_host = NULL;
int eol_nulls = 0;
int protect_args = -1;
int old_style_args = -1;
int human_readable = 1;
int recurse = 0;
int mkpath_dest_arg = 0;
int allow_inc_recurse = 1;
int xfer_dirs = -1;
int am_daemon = 0;
int connect_timeout = 0;
int keep_partial = 0;
int safe_symlinks = 0;
int copy_unsafe_links = 0;
int munge_symlinks = 0;
int size_only = 0;
int daemon_bwlimit = 0;
int bwlimit = 0;
int fuzzy_basis = 0;
size_t bwlimit_writemax = 0;
int ignore_existing = 0;
int ignore_non_existing = 0;
int need_messages_from_generator = 0;
int max_delete = INT_MIN;
OFF_T max_size = -1;
OFF_T min_size = -1;
int ignore_errors = 0;
int modify_window = 0;
int blocking_io = -1;
int checksum_seed = 0;
int inplace = 0;
int delay_updates = 0;
int32 block_size = 0;
time_t stop_at_utime = 0;
char *skip_compress = NULL;
char *copy_as = NULL;
item_list dparam_list = EMPTY_ITEM_LIST;

/** Network address family. **/
int default_af_hint
#ifdef INET6
	= 0;		/* Any protocol */
#else
	= AF_INET;	/* Must use IPv4 */
# ifdef AF_INET6
#  undef AF_INET6
# endif
# define AF_INET6 AF_INET /* make -6 option a no-op */
#endif

/** Do not go into the background when run as --daemon.  Good
 * for debugging and required for running as a service on W32,
 * or under Unix process-monitors. **/
int no_detach
#if defined _WIN32 || defined __WIN32__
	= 1;
#else
	= 0;
#endif

int write_batch = 0;
int read_batch = 0;
int backup_dir_len = 0;
int backup_suffix_len;
unsigned int backup_dir_remainder;

char *backup_suffix = NULL;
char *tmpdir = NULL;
char *partial_dir = NULL;
char *basis_dir[MAX_BASIS_DIRS+1];
char *config_file = NULL;
char *shell_cmd = NULL;
char *logfile_name = NULL;
char *logfile_format = NULL;
char *stdout_format = NULL;
char *password_file = NULL;
char *early_input_file = NULL;
char *rsync_path = RSYNC_PATH;
char *backup_dir = NULL;
char backup_dir_buf[MAXPATHLEN];
char *sockopts = NULL;
char *usermap = NULL;
char *groupmap = NULL;
int rsync_port = 0;
int alt_dest_type = 0;
int basis_dir_cnt = 0;

#define DEFAULT_MAX_ALLOC (1024L * 1024 * 1024)
size_t max_alloc = DEFAULT_MAX_ALLOC;
char *max_alloc_arg;

static int version_opt_cnt = 0;
static int remote_option_alloc = 0;
int remote_option_cnt = 0;
const char **remote_options = NULL;
const char *checksum_choice = NULL;
const char *compress_choice = NULL;
static const char *empty_argv[1];

int quiet = 0;
int output_motd = 1;
int log_before_transfer = 0;
int stdout_format_has_i = 0;
int stdout_format_has_o_or_i = 0;
int logfile_format_has_i = 0;
int logfile_format_has_o_or_i = 0;
int always_checksum = 0;
int list_only = 0;

#define MAX_BATCH_NAME_LEN 256	/* Must be less than MAXPATHLEN-13 */
char *batch_name = NULL;

int need_unsorted_flist = 0;
char *iconv_opt =
#ifdef ICONV_OPTION
		ICONV_OPTION;
#else
		NULL;
#endif

struct chmod_mode_struct *chmod_modes = NULL;

static const char *debug_verbosity[] = {
	/*0*/ NULL,
	/*1*/ NULL,
	/*2*/ "BIND,CMD,CONNECT,DEL,DELTASUM,DUP,FILTER,FLIST,ICONV",
	/*3*/ "ACL,BACKUP,CONNECT2,DELTASUM2,DEL2,EXIT,FILTER2,FLIST2,FUZZY,GENR,OWN,RECV,SEND,TIME",
	/*4*/ "CMD2,DELTASUM3,DEL3,EXIT2,FLIST3,ICONV2,OWN2,PROTO,TIME2",
	/*5*/ "CHDIR,DELTASUM4,FLIST4,FUZZY2,HASH,HLINK",
};

#define MAX_VERBOSITY ((int)(sizeof debug_verbosity / sizeof debug_verbosity[0]) - 1)

static const char *info_verbosity[1+MAX_VERBOSITY] = {
	/*0*/ "NONREG",
	/*1*/ "COPY,DEL,FLIST,MISC,NAME,STATS,SYMSAFE",
	/*2*/ "BACKUP,MISC2,MOUNT,NAME2,REMOVE,SKIP",
};

#define MAX_OUT_LEVEL 4 /* The largest N allowed for any flagN word. */

short info_levels[COUNT_INFO], debug_levels[COUNT_DEBUG];

#define DEFAULT_PRIORITY 0 	/* Default/implied/--verbose set values. */
#define HELP_PRIORITY 1		/* The help output uses this level. */
#define USER_PRIORITY 2		/* User-specified via --info or --debug */
#define LIMIT_PRIORITY 3	/* Overriding priority when limiting values. */

#define W_CLI (1<<0)	/* client side */
#define W_SRV (1<<1)	/* server side */
#define W_SND (1<<2)	/* sending side */
#define W_REC (1<<3)	/* receiving side */

struct output_struct {
	char *name;	/* The name of the info/debug flag. */
	char *help;	/* The description of the info/debug flag. */
	uchar namelen;  /* The length of the name string. */
	uchar flag;	/* The flag's value, for consistency check. */
	uchar where;	/* Bits indicating where the flag is used. */
	uchar priority; /* See *_PRIORITY defines. */
};

#define INFO_WORD(flag, where, help) { #flag, help, sizeof #flag - 1, INFO_##flag, where, 0 }

static struct output_struct info_words[COUNT_INFO+1] = {
	INFO_WORD(BACKUP, W_REC, "Mention files backed up"),
	INFO_WORD(COPY, W_REC, "Mention files copied locally on the receiving side"),
	INFO_WORD(DEL, W_REC, "Mention deletions on the receiving side"),
	INFO_WORD(FLIST, W_CLI, "Mention file-list receiving/sending (levels 1-2)"),
	INFO_WORD(MISC, W_SND|W_REC, "Mention miscellaneous information (levels 1-2)"),
	INFO_WORD(MOUNT, W_SND|W_REC, "Mention mounts that were found or skipped"),
	INFO_WORD(NAME, W_SND|W_REC, "Mention 1) updated file/dir names, 2) unchanged names"),
	INFO_WORD(NONREG, W_REC, "Mention skipped non-regular files (default 1, 0 disables)"),
	INFO_WORD(PROGRESS, W_CLI, "Mention 1) per-file progress or 2) total transfer progress"),
	INFO_WORD(REMOVE, W_SND, "Mention files removed on the sending side"),
	INFO_WORD(SKIP, W_REC, "Mention files skipped due to transfer overrides (levels 1-2)"),
	INFO_WORD(STATS, W_CLI|W_SRV, "Mention statistics at end of run (levels 1-3)"),
	INFO_WORD(SYMSAFE, W_SND|W_REC, "Mention symlinks that are unsafe"),
	{ NULL, "--info", 0, 0, 0, 0 }
};

#define DEBUG_WORD(flag, where, help) { #flag, help, sizeof #flag - 1, DEBUG_##flag, where, 0 }

static struct output_struct debug_words[COUNT_DEBUG+1] = {
	DEBUG_WORD(ACL, W_SND|W_REC, "Debug extra ACL info"),
	DEBUG_WORD(BACKUP, W_REC, "Debug backup actions (levels 1-2)"),
	DEBUG_WORD(BIND, W_CLI, "Debug socket bind actions"),
	DEBUG_WORD(CHDIR, W_CLI|W_SRV, "Debug when the current directory changes"),
	DEBUG_WORD(CONNECT, W_CLI, "Debug connection events (levels 1-2)"),
	DEBUG_WORD(CMD, W_CLI, "Debug commands+options that are issued (levels 1-2)"),
	DEBUG_WORD(DEL, W_REC, "Debug delete actions (levels 1-3)"),
	DEBUG_WORD(DELTASUM, W_SND|W_REC, "Debug delta-transfer checksumming (levels 1-4)"),
	DEBUG_WORD(DUP, W_REC, "Debug weeding of duplicate names"),
	DEBUG_WORD(EXIT, W_CLI|W_SRV, "Debug exit events (levels 1-3)"),
	DEBUG_WORD(FILTER, W_SND|W_REC, "Debug filter actions (levels 1-3)"),
	DEBUG_WORD(FLIST, W_SND|W_REC, "Debug file-list operations (levels 1-4)"),
	DEBUG_WORD(FUZZY, W_REC, "Debug fuzzy scoring (levels 1-2)"),
	DEBUG_WORD(GENR, W_REC, "Debug generator functions"),
	DEBUG_WORD(HASH, W_SND|W_REC, "Debug hashtable code"),
	DEBUG_WORD(HLINK, W_SND|W_REC, "Debug hard-link actions (levels 1-3)"),
	DEBUG_WORD(ICONV, W_CLI|W_SRV, "Debug iconv character conversions (levels 1-2)"),
	DEBUG_WORD(IO, W_CLI|W_SRV, "Debug I/O routines (levels 1-4)"),
	DEBUG_WORD(NSTR, W_CLI|W_SRV, "Debug negotiation strings"),
	DEBUG_WORD(OWN, W_REC, "Debug ownership changes in users & groups (levels 1-2)"),
	DEBUG_WORD(PROTO, W_CLI|W_SRV, "Debug protocol information"),
	DEBUG_WORD(RECV, W_REC, "Debug receiver functions"),
	DEBUG_WORD(SEND, W_SND, "Debug sender functions"),
	DEBUG_WORD(TIME, W_REC, "Debug setting of modified times (levels 1-2)"),
	{ NULL, "--debug", 0, 0, 0, 0 }
};

static int verbose = 0;
static int do_stats = 0;
static int do_progress = 0;
static int daemon_opt;   /* sets am_daemon after option error-reporting */
static int F_option_cnt = 0;
static int modify_window_set;
static int itemize_changes = 0;
static int refused_delete, refused_archive_part, refused_compress;
static int refused_partial, refused_progress, refused_delete_before;
static int refused_delete_during;
static int refused_inplace, refused_no_iconv;
static BOOL usermap_via_chown, groupmap_via_chown;
static char *outbuf_mode;
static char *bwlimit_arg, *max_size_arg, *min_size_arg;
static char tmp_partialdir[] = ".~tmp~";

/** Local address to bind.  As a character string because it's
 * interpreted by the IPv6 layer: should be a numeric IP4 or IP6
 * address, or a hostname. **/
char *bind_address;

static void output_item_help(struct output_struct *words);

/* This constructs a string that represents all the options set for either
 * the --info or --debug setting, skipping any implied options (by -v, etc.).
 * This is used both when conveying the user's options to the server, and
 * when the help output wants to tell the user what options are implied. */
static char *make_output_option(struct output_struct *words, short *levels, uchar where)
{
	char *str = words == info_words ? "--info=" : "--debug=";
	int j, counts[MAX_OUT_LEVEL+1], pos, skipped = 0, len = 0, max = 0, lev = 0;
	int word_count = words == info_words ? COUNT_INFO : COUNT_DEBUG;
	char *buf;

	memset(counts, 0, sizeof counts);

	for (j = 0; words[j].name; j++) {
		if (words[j].flag != j) {
			rprintf(FERROR, "rsync: internal error on %s%s: %d != %d\n",
				words == info_words ? "INFO_" : "DEBUG_",
				words[j].name, words[j].flag, j);
			exit_cleanup(RERR_UNSUPPORTED);
		}
		if (!(words[j].where & where))
			continue;
		if (words[j].priority == DEFAULT_PRIORITY) {
			/* Implied items don't need to be mentioned. */
			skipped++;
			continue;
		}
		len += len ? 1 : strlen(str);
		len += strlen(words[j].name);
		len += levels[j] == 1 ? 0 : 1;

		if (words[j].priority == HELP_PRIORITY)
			continue; /* no abbreviating for help */

		assert(levels[j] <= MAX_OUT_LEVEL);
		if (++counts[levels[j]] > max) {
			/* Determine which level has the most items. */
			lev = levels[j];
			max = counts[lev];
		}
	}

	/* Sanity check the COUNT_* define against the length of the table. */
	if (j != word_count) {
		rprintf(FERROR, "rsync: internal error: %s is wrong! (%d != %d)\n",
			words == info_words ? "COUNT_INFO" : "COUNT_DEBUG",
			j, word_count);
		exit_cleanup(RERR_UNSUPPORTED);
	}

	if (!len)
		return NULL;

	len++;
	buf = new_array(char, len);
	pos = 0;

	if (skipped || max < 5)
		lev = -1;
	else {
		if (lev == 0)
			pos += snprintf(buf, len, "%sNONE", str);
		else if (lev == 1)
			pos += snprintf(buf, len, "%sALL", str);
		else
			pos += snprintf(buf, len, "%sALL%d", str, lev);
	}

	for (j = 0; words[j].name && pos < len; j++) {
		if (words[j].priority == DEFAULT_PRIORITY || levels[j] == lev || !(words[j].where & where))
			continue;
		if (pos)
			buf[pos++] = ',';
		else
			pos += strlcpy(buf+pos, str, len-pos);
		if (pos < len)
			pos += strlcpy(buf+pos, words[j].name, len-pos);
		/* Level 1 is implied by the name alone. */
		if (levels[j] != 1 && pos < len)
			buf[pos++] = '0' + levels[j];
	}

	buf[pos] = '\0';

	return buf;
}

static void parse_output_words(struct output_struct *words, short *levels, const char *str, uchar priority)
{
	const char *s;
	int j, len, lev;

	for ( ; str; str = s) {
		if ((s = strchr(str, ',')) != NULL)
			len = s++ - str;
		else
			len = strlen(str);
		if (!len)
			continue;
		if (!isDigit(str)) {
			while (len && isDigit(str+len-1))
				len--;
		}
		lev = isDigit(str+len) ? atoi(str+len) : 1;
		if (lev > MAX_OUT_LEVEL)
			lev = MAX_OUT_LEVEL;
		if (len == 4 && strncasecmp(str, "help", 4) == 0) {
			output_item_help(words);
			exit_cleanup(0);
		}
		if (len == 4 && strncasecmp(str, "none", 4) == 0)
			len = lev = 0;
		else if (len == 3 && strncasecmp(str, "all", 3) == 0)
			len = 0;
		for (j = 0; words[j].name; j++) {
			if (!len
			 || (len == words[j].namelen && strncasecmp(str, words[j].name, len) == 0)) {
				if (priority >= words[j].priority) {
					words[j].priority = priority;
					levels[j] = lev;
				}
				if (len)
					break;
			}
		}
		if (len && !words[j].name && !am_server) {
			rprintf(FERROR, "Unknown %s item: \"%.*s\"\n",
				words[j].help, len, str);
			exit_cleanup(RERR_SYNTAX);
		}
	}
}

/* Tell the user what all the info or debug flags mean. */
static void output_item_help(struct output_struct *words)
{
	short *levels = words == info_words ? info_levels : debug_levels;
	const char **verbosity = words == info_words ? info_verbosity : debug_verbosity;
	char buf[128], *opt, *fmt = "%-10s %s\n";
	int j;

	reset_output_levels();

	rprintf(FINFO, "Use OPT or OPT1 for level 1 output, OPT2 for level 2, etc.; OPT0 silences.\n");
	rprintf(FINFO, "\n");
	for (j = 0; words[j].name; j++)
		rprintf(FINFO, fmt, words[j].name, words[j].help);
	rprintf(FINFO, "\n");

	snprintf(buf, sizeof buf, "Set all %s options (e.g. all%d)",
		 words[j].help, MAX_OUT_LEVEL);
	rprintf(FINFO, fmt, "ALL", buf);

	snprintf(buf, sizeof buf, "Silence all %s options (same as all0)",
		 words[j].help);
	rprintf(FINFO, fmt, "NONE", buf);

	rprintf(FINFO, fmt, "HELP", "Output this help message");
	rprintf(FINFO, "\n");
	rprintf(FINFO, "Options added at each level of verbosity:\n");

	for (j = 0; j <= MAX_VERBOSITY; j++) {
		parse_output_words(words, levels, verbosity[j], HELP_PRIORITY);
		opt = make_output_option(words, levels, W_CLI|W_SRV|W_SND|W_REC);
		if (opt) {
			rprintf(FINFO, "%d) %s\n", j, strchr(opt, '=')+1);
			free(opt);
		}
		reset_output_levels();
	}
}

/* The --verbose option now sets info+debug flags. */
static void set_output_verbosity(int level, uchar priority)
{
	int j;

	if (level > MAX_VERBOSITY)
		level = MAX_VERBOSITY;

	for (j = 0; j <= level; j++) {
		parse_output_words(info_words, info_levels, info_verbosity[j], priority);
		parse_output_words(debug_words, debug_levels, debug_verbosity[j], priority);
	}
}

/* Limit the info+debug flag levels given a verbose-option level limit. */
void limit_output_verbosity(int level)
{
	short info_limits[COUNT_INFO], debug_limits[COUNT_DEBUG];
	int j;

	if (level > MAX_VERBOSITY)
		return;

	memset(info_limits, 0, sizeof info_limits);
	memset(debug_limits, 0, sizeof debug_limits);

	/* Compute the level limits in the above arrays. */
	for (j = 0; j <= level; j++) {
		parse_output_words(info_words, info_limits, info_verbosity[j], LIMIT_PRIORITY);
		parse_output_words(debug_words, debug_limits, debug_verbosity[j], LIMIT_PRIORITY);
	}

	for (j = 0; j < COUNT_INFO; j++) {
		if (info_levels[j] > info_limits[j])
			info_levels[j] = info_limits[j];
	}

	for (j = 0; j < COUNT_DEBUG; j++) {
		if (debug_levels[j] > debug_limits[j])
			debug_levels[j] = debug_limits[j];
	}
}

void reset_output_levels(void)
{
	int j;

	memset(info_levels, 0, sizeof info_levels);
	memset(debug_levels, 0, sizeof debug_levels);

	for (j = 0; j < COUNT_INFO; j++)
		info_words[j].priority = DEFAULT_PRIORITY;

	for (j = 0; j < COUNT_DEBUG; j++)
		debug_words[j].priority = DEFAULT_PRIORITY;
}

void negate_output_levels(void)
{
	int j;

	for (j = 0; j < COUNT_INFO; j++)
		info_levels[j] *= -1;

	for (j = 0; j < COUNT_DEBUG; j++)
		debug_levels[j] *= -1;
}

enum {OPT_SERVER = 1000, OPT_DAEMON, OPT_SENDER, OPT_EXCLUDE, OPT_EXCLUDE_FROM,
      OPT_FILTER, OPT_COMPARE_DEST, OPT_COPY_DEST, OPT_LINK_DEST, OPT_HELP,
      OPT_INCLUDE, OPT_INCLUDE_FROM, OPT_MODIFY_WINDOW, OPT_MIN_SIZE, OPT_CHMOD,
      OPT_READ_BATCH, OPT_WRITE_BATCH, OPT_ONLY_WRITE_BATCH, OPT_MAX_SIZE,
      OPT_NO_D, OPT_APPEND, OPT_NO_ICONV, OPT_INFO, OPT_DEBUG, OPT_BLOCK_SIZE,
      OPT_USERMAP, OPT_GROUPMAP, OPT_CHOWN, OPT_BWLIMIT, OPT_STDERR,
      OPT_OLD_COMPRESS, OPT_NEW_COMPRESS, OPT_NO_COMPRESS, OPT_OLD_ARGS,
      OPT_STOP_AFTER, OPT_STOP_AT,
      OPT_REFUSED_BASE = 9000};

static struct poptOption long_options[] = {
  /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
  {"help",             0,  POPT_ARG_NONE,   0, OPT_HELP, 0, 0 },
  {"version",         'V', POPT_ARG_NONE,   0, 'V', 0, 0},
  {"verbose",         'v', POPT_ARG_NONE,   0, 'v', 0, 0 },
  {"no-verbose",       0,  POPT_ARG_VAL,    &verbose, 0, 0, 0 },
  {"no-v",             0,  POPT_ARG_VAL,    &verbose, 0, 0, 0 },
  {"info",             0,  POPT_ARG_STRING, 0, OPT_INFO, 0, 0 },
  {"debug",            0,  POPT_ARG_STRING, 0, OPT_DEBUG, 0, 0 },
  {"stderr",           0,  POPT_ARG_STRING, 0, OPT_STDERR, 0, 0 },
  {"msgs2stderr",      0,  POPT_ARG_VAL,    &msgs2stderr, 1, 0, 0 },
  {"no-msgs2stderr",   0,  POPT_ARG_VAL,    &msgs2stderr, 0, 0, 0 },
  {"quiet",           'q', POPT_ARG_NONE,   0, 'q', 0, 0 },
  {"motd",             0,  POPT_ARG_VAL,    &output_motd, 1, 0, 0 },
  {"no-motd",          0,  POPT_ARG_VAL,    &output_motd, 0, 0, 0 },
  {"stats",            0,  POPT_ARG_NONE,   &do_stats, 0, 0, 0 },
  {"human-readable",  'h', POPT_ARG_NONE,   0, 'h', 0, 0},
  {"no-human-readable",0,  POPT_ARG_VAL,    &human_readable, 0, 0, 0},
  {"no-h",             0,  POPT_ARG_VAL,    &human_readable, 0, 0, 0},
  {"dry-run",         'n', POPT_ARG_NONE,   &dry_run, 0, 0, 0 },
  {"archive",         'a', POPT_ARG_NONE,   0, 'a', 0, 0 },
  {"recursive",       'r', POPT_ARG_VAL,    &recurse, 2, 0, 0 },
  {"no-recursive",     0,  POPT_ARG_VAL,    &recurse, 0, 0, 0 },
  {"no-r",             0,  POPT_ARG_VAL,    &recurse, 0, 0, 0 },
  {"inc-recursive",    0,  POPT_ARG_VAL,    &allow_inc_recurse, 1, 0, 0 },
  {"no-inc-recursive", 0,  POPT_ARG_VAL,    &allow_inc_recurse, 0, 0, 0 },
  {"i-r",              0,  POPT_ARG_VAL,    &allow_inc_recurse, 1, 0, 0 },
  {"no-i-r",           0,  POPT_ARG_VAL,    &allow_inc_recurse, 0, 0, 0 },
  {"dirs",            'd', POPT_ARG_VAL,    &xfer_dirs, 2, 0, 0 },
  {"no-dirs",          0,  POPT_ARG_VAL,    &xfer_dirs, 0, 0, 0 },
  {"no-d",             0,  POPT_ARG_VAL,    &xfer_dirs, 0, 0, 0 },
  {"old-dirs",         0,  POPT_ARG_VAL,    &xfer_dirs, 4, 0, 0 },
  {"old-d",            0,  POPT_ARG_VAL,    &xfer_dirs, 4, 0, 0 },
  {"perms",           'p', POPT_ARG_VAL,    &preserve_perms, 1, 0, 0 },
  {"no-perms",         0,  POPT_ARG_VAL,    &preserve_perms, 0, 0, 0 },
  {"no-p",             0,  POPT_ARG_VAL,    &preserve_perms, 0, 0, 0 },
  {"executability",   'E', POPT_ARG_NONE,   &preserve_executability, 0, 0, 0 },
  {"acls",            'A', POPT_ARG_NONE,   0, 'A', 0, 0 },
  {"no-acls",          0,  POPT_ARG_VAL,    &preserve_acls, 0, 0, 0 },
  {"no-A",             0,  POPT_ARG_VAL,    &preserve_acls, 0, 0, 0 },
  {"xattrs",          'X', POPT_ARG_NONE,   0, 'X', 0, 0 },
  {"no-xattrs",        0,  POPT_ARG_VAL,    &preserve_xattrs, 0, 0, 0 },
  {"no-X",             0,  POPT_ARG_VAL,    &preserve_xattrs, 0, 0, 0 },
  {"times",           't', POPT_ARG_VAL,    &preserve_mtimes, 1, 0, 0 },
  {"no-times",         0,  POPT_ARG_VAL,    &preserve_mtimes, 0, 0, 0 },
  {"no-t",             0,  POPT_ARG_VAL,    &preserve_mtimes, 0, 0, 0 },
  {"atimes",          'U', POPT_ARG_NONE,   0, 'U', 0, 0 },
  {"no-atimes",        0,  POPT_ARG_VAL,    &preserve_atimes, 0, 0, 0 },
  {"no-U",             0,  POPT_ARG_VAL,    &preserve_atimes, 0, 0, 0 },
  {"open-noatime",     0,  POPT_ARG_VAL,    &open_noatime, 1, 0, 0 },
  {"no-open-noatime",  0,  POPT_ARG_VAL,    &open_noatime, 0, 0, 0 },
  {"crtimes",         'N', POPT_ARG_VAL,    &preserve_crtimes, 1, 0, 0 },
  {"no-crtimes",       0,  POPT_ARG_VAL,    &preserve_crtimes, 0, 0, 0 },
  {"no-N",             0,  POPT_ARG_VAL,    &preserve_crtimes, 0, 0, 0 },
  {"omit-dir-times",  'O', POPT_ARG_VAL,    &omit_dir_times, 1, 0, 0 },
  {"no-omit-dir-times",0,  POPT_ARG_VAL,    &omit_dir_times, 0, 0, 0 },
  {"no-O",             0,  POPT_ARG_VAL,    &omit_dir_times, 0, 0, 0 },
  {"omit-link-times", 'J', POPT_ARG_VAL,    &omit_link_times, 1, 0, 0 },
  {"no-omit-link-times",0, POPT_ARG_VAL,    &omit_link_times, 0, 0, 0 },
  {"no-J",             0,  POPT_ARG_VAL,    &omit_link_times, 0, 0, 0 },
  {"modify-window",   '@', POPT_ARG_INT,    &modify_window, OPT_MODIFY_WINDOW, 0, 0 },
  {"super",            0,  POPT_ARG_VAL,    &am_root, 2, 0, 0 },
  {"no-super",         0,  POPT_ARG_VAL,    &am_root, 0, 0, 0 },
  {"fake-super",       0,  POPT_ARG_VAL,    &am_root, -1, 0, 0 },
  {"owner",           'o', POPT_ARG_VAL,    &preserve_uid, 1, 0, 0 },
  {"no-owner",         0,  POPT_ARG_VAL,    &preserve_uid, 0, 0, 0 },
  {"no-o",             0,  POPT_ARG_VAL,    &preserve_uid, 0, 0, 0 },
  {"group",           'g', POPT_ARG_VAL,    &preserve_gid, 1, 0, 0 },
  {"no-group",         0,  POPT_ARG_VAL,    &preserve_gid, 0, 0, 0 },
  {"no-g",             0,  POPT_ARG_VAL,    &preserve_gid, 0, 0, 0 },
  {0,                 'D', POPT_ARG_NONE,   0, 'D', 0, 0 },
  {"no-D",             0,  POPT_ARG_NONE,   0, OPT_NO_D, 0, 0 },
  {"devices",          0,  POPT_ARG_VAL,    &preserve_devices, 1, 0, 0 },
  {"no-devices",       0,  POPT_ARG_VAL,    &preserve_devices, 0, 0, 0 },
  {"copy-devices",     0,  POPT_ARG_NONE,   &copy_devices, 0, 0, 0 },
  {"write-devices",    0,  POPT_ARG_VAL,    &write_devices, 1, 0, 0 },
  {"no-write-devices", 0,  POPT_ARG_VAL,    &write_devices, 0, 0, 0 },
  {"specials",         0,  POPT_ARG_VAL,    &preserve_specials, 1, 0, 0 },
  {"no-specials",      0,  POPT_ARG_VAL,    &preserve_specials, 0, 0, 0 },
  {"links",           'l', POPT_ARG_VAL,    &preserve_links, 1, 0, 0 },
  {"no-links",         0,  POPT_ARG_VAL,    &preserve_links, 0, 0, 0 },
  {"no-l",             0,  POPT_ARG_VAL,    &preserve_links, 0, 0, 0 },
  {"copy-links",      'L', POPT_ARG_NONE,   &copy_links, 0, 0, 0 },
  {"copy-unsafe-links",0,  POPT_ARG_NONE,   &copy_unsafe_links, 0, 0, 0 },
  {"safe-links",       0,  POPT_ARG_NONE,   &safe_symlinks, 0, 0, 0 },
  {"munge-links",      0,  POPT_ARG_VAL,    &munge_symlinks, 1, 0, 0 },
  {"no-munge-links",   0,  POPT_ARG_VAL,    &munge_symlinks, 0, 0, 0 },
  {"copy-dirlinks",   'k', POPT_ARG_NONE,   &copy_dirlinks, 0, 0, 0 },
  {"keep-dirlinks",   'K', POPT_ARG_NONE,   &keep_dirlinks, 0, 0, 0 },
  {"hard-links",      'H', POPT_ARG_NONE,   0, 'H', 0, 0 },
  {"no-hard-links",    0,  POPT_ARG_VAL,    &preserve_hard_links, 0, 0, 0 },
  {"no-H",             0,  POPT_ARG_VAL,    &preserve_hard_links, 0, 0, 0 },
  {"relative",        'R', POPT_ARG_VAL,    &relative_paths, 1, 0, 0 },
  {"no-relative",      0,  POPT_ARG_VAL,    &relative_paths, 0, 0, 0 },
  {"no-R",             0,  POPT_ARG_VAL,    &relative_paths, 0, 0, 0 },
  {"implied-dirs",     0,  POPT_ARG_VAL,    &implied_dirs, 1, 0, 0 },
  {"no-implied-dirs",  0,  POPT_ARG_VAL,    &implied_dirs, 0, 0, 0 },
  {"i-d",              0,  POPT_ARG_VAL,    &implied_dirs, 1, 0, 0 },
  {"no-i-d",           0,  POPT_ARG_VAL,    &implied_dirs, 0, 0, 0 },
  {"chmod",            0,  POPT_ARG_STRING, 0, OPT_CHMOD, 0, 0 },
  {"ignore-times",    'I', POPT_ARG_NONE,   &ignore_times, 0, 0, 0 },
  {"size-only",        0,  POPT_ARG_NONE,   &size_only, 0, 0, 0 },
  {"one-file-system", 'x', POPT_ARG_NONE,   0, 'x', 0, 0 },
  {"no-one-file-system",0, POPT_ARG_VAL,    &one_file_system, 0, 0, 0 },
  {"no-x",             0,  POPT_ARG_VAL,    &one_file_system, 0, 0, 0 },
  {"update",          'u', POPT_ARG_NONE,   &update_only, 0, 0, 0 },
  {"existing",         0,  POPT_ARG_NONE,   &ignore_non_existing, 0, 0, 0 },
  {"ignore-non-existing",0,POPT_ARG_NONE,   &ignore_non_existing, 0, 0, 0 },
  {"ignore-existing",  0,  POPT_ARG_NONE,   &ignore_existing, 0, 0, 0 },
  {"max-size",         0,  POPT_ARG_STRING, &max_size_arg, OPT_MAX_SIZE, 0, 0 },
  {"min-size",         0,  POPT_ARG_STRING, &min_size_arg, OPT_MIN_SIZE, 0, 0 },
  {"max-alloc",        0,  POPT_ARG_STRING, &max_alloc_arg, 0, 0, 0 },
  {"sparse",          'S', POPT_ARG_VAL,    &sparse_files, 1, 0, 0 },
  {"no-sparse",        0,  POPT_ARG_VAL,    &sparse_files, 0, 0, 0 },
  {"no-S",             0,  POPT_ARG_VAL,    &sparse_files, 0, 0, 0 },
  {"preallocate",      0,  POPT_ARG_NONE,   &preallocate_files, 0, 0, 0},
  {"inplace",          0,  POPT_ARG_VAL,    &inplace, 1, 0, 0 },
  {"no-inplace",       0,  POPT_ARG_VAL,    &inplace, 0, 0, 0 },
  {"append",           0,  POPT_ARG_NONE,   0, OPT_APPEND, 0, 0 },
  {"append-verify",    0,  POPT_ARG_VAL,    &append_mode, 2, 0, 0 },
  {"no-append",        0,  POPT_ARG_VAL,    &append_mode, 0, 0, 0 },
  {"del",              0,  POPT_ARG_NONE,   &delete_during, 0, 0, 0 },
  {"delete",           0,  POPT_ARG_NONE,   &delete_mode, 0, 0, 0 },
  {"delete-before",    0,  POPT_ARG_NONE,   &delete_before, 0, 0, 0 },
  {"delete-during",    0,  POPT_ARG_VAL,    &delete_during, 1, 0, 0 },
  {"delete-delay",     0,  POPT_ARG_VAL,    &delete_during, 2, 0, 0 },
  {"delete-after",     0,  POPT_ARG_NONE,   &delete_after, 0, 0, 0 },
  {"delete-excluded",  0,  POPT_ARG_NONE,   &delete_excluded, 0, 0, 0 },
  {"delete-missing-args",0,POPT_BIT_SET,    &missing_args, 2, 0, 0 },
  {"ignore-missing-args",0,POPT_BIT_SET,    &missing_args, 1, 0, 0 },
  {"remove-sent-files",0,  POPT_ARG_VAL,    &remove_source_files, 2, 0, 0 }, /* deprecated */
  {"remove-source-files",0,POPT_ARG_VAL,    &remove_source_files, 1, 0, 0 },
  {"force",            0,  POPT_ARG_VAL,    &force_delete, 1, 0, 0 },
  {"no-force",         0,  POPT_ARG_VAL,    &force_delete, 0, 0, 0 },
  {"ignore-errors",    0,  POPT_ARG_VAL,    &ignore_errors, 1, 0, 0 },
  {"no-ignore-errors", 0,  POPT_ARG_VAL,    &ignore_errors, 0, 0, 0 },
  {"max-delete",       0,  POPT_ARG_INT,    &max_delete, 0, 0, 0 },
  {0,                 'F', POPT_ARG_NONE,   0, 'F', 0, 0 },
  {"filter",          'f', POPT_ARG_STRING, 0, OPT_FILTER, 0, 0 },
  {"exclude",          0,  POPT_ARG_STRING, 0, OPT_EXCLUDE, 0, 0 },
  {"include",          0,  POPT_ARG_STRING, 0, OPT_INCLUDE, 0, 0 },
  {"exclude-from",     0,  POPT_ARG_STRING, 0, OPT_EXCLUDE_FROM, 0, 0 },
  {"include-from",     0,  POPT_ARG_STRING, 0, OPT_INCLUDE_FROM, 0, 0 },
  {"cvs-exclude",     'C', POPT_ARG_NONE,   &cvs_exclude, 0, 0, 0 },
  {"whole-file",      'W', POPT_ARG_VAL,    &whole_file, 1, 0, 0 },
  {"no-whole-file",    0,  POPT_ARG_VAL,    &whole_file, 0, 0, 0 },
  {"no-W",             0,  POPT_ARG_VAL,    &whole_file, 0, 0, 0 },
  {"checksum",        'c', POPT_ARG_VAL,    &always_checksum, 1, 0, 0 },
  {"no-checksum",      0,  POPT_ARG_VAL,    &always_checksum, 0, 0, 0 },
  {"no-c",             0,  POPT_ARG_VAL,    &always_checksum, 0, 0, 0 },
  {"checksum-choice",  0,  POPT_ARG_STRING, &checksum_choice, 0, 0, 0 },
  {"cc",               0,  POPT_ARG_STRING, &checksum_choice, 0, 0, 0 },
  {"block-size",      'B', POPT_ARG_STRING, 0, OPT_BLOCK_SIZE, 0, 0 },
  {"compare-dest",     0,  POPT_ARG_STRING, 0, OPT_COMPARE_DEST, 0, 0 },
  {"copy-dest",        0,  POPT_ARG_STRING, 0, OPT_COPY_DEST, 0, 0 },
  {"link-dest",        0,  POPT_ARG_STRING, 0, OPT_LINK_DEST, 0, 0 },
  {"fuzzy",           'y', POPT_ARG_NONE,   0, 'y', 0, 0 },
  {"no-fuzzy",         0,  POPT_ARG_VAL,    &fuzzy_basis, 0, 0, 0 },
  {"no-y",             0,  POPT_ARG_VAL,    &fuzzy_basis, 0, 0, 0 },
  {"compress",        'z', POPT_ARG_NONE,   0, 'z', 0, 0 },
  {"old-compress",     0,  POPT_ARG_NONE,   0, OPT_OLD_COMPRESS, 0, 0 },
  {"new-compress",     0,  POPT_ARG_NONE,   0, OPT_NEW_COMPRESS, 0, 0 },
  {"no-compress",      0,  POPT_ARG_NONE,   0, OPT_NO_COMPRESS, 0, 0 },
  {"no-z",             0,  POPT_ARG_NONE,   0, OPT_NO_COMPRESS, 0, 0 },
  {"compress-choice",  0,  POPT_ARG_STRING, &compress_choice, 0, 0, 0 },
  {"zc",               0,  POPT_ARG_STRING, &compress_choice, 0, 0, 0 },
  {"skip-compress",    0,  POPT_ARG_STRING, &skip_compress, 0, 0, 0 },
  {"compress-level",   0,  POPT_ARG_INT,    &do_compression_level, 0, 0, 0 },
  {"zl",               0,  POPT_ARG_INT,    &do_compression_level, 0, 0, 0 },
  {0,                 'P', POPT_ARG_NONE,   0, 'P', 0, 0 },
  {"progress",         0,  POPT_ARG_VAL,    &do_progress, 1, 0, 0 },
  {"no-progress",      0,  POPT_ARG_VAL,    &do_progress, 0, 0, 0 },
  {"partial",          0,  POPT_ARG_VAL,    &keep_partial, 1, 0, 0 },
  {"no-partial",       0,  POPT_ARG_VAL,    &keep_partial, 0, 0, 0 },
  {"partial-dir",      0,  POPT_ARG_STRING, &partial_dir, 0, 0, 0 },
  {"delay-updates",    0,  POPT_ARG_VAL,    &delay_updates, 1, 0, 0 },
  {"no-delay-updates", 0,  POPT_ARG_VAL,    &delay_updates, 0, 0, 0 },
  {"prune-empty-dirs",'m', POPT_ARG_VAL,    &prune_empty_dirs, 1, 0, 0 },
  {"no-prune-empty-dirs",0,POPT_ARG_VAL,    &prune_empty_dirs, 0, 0, 0 },
  {"no-m",             0,  POPT_ARG_VAL,    &prune_empty_dirs, 0, 0, 0 },
  {"log-file",         0,  POPT_ARG_STRING, &logfile_name, 0, 0, 0 },
  {"log-file-format",  0,  POPT_ARG_STRING, &logfile_format, 0, 0, 0 },
  {"out-format",       0,  POPT_ARG_STRING, &stdout_format, 0, 0, 0 },
  {"log-format",       0,  POPT_ARG_STRING, &stdout_format, 0, 0, 0 }, /* DEPRECATED */
  {"itemize-changes", 'i', POPT_ARG_NONE,   0, 'i', 0, 0 },
  {"no-itemize-changes",0, POPT_ARG_VAL,    &itemize_changes, 0, 0, 0 },
  {"no-i",             0,  POPT_ARG_VAL,    &itemize_changes, 0, 0, 0 },
  {"bwlimit",          0,  POPT_ARG_STRING, &bwlimit_arg, OPT_BWLIMIT, 0, 0 },
  {"no-bwlimit",       0,  POPT_ARG_VAL,    &bwlimit, 0, 0, 0 },
  {"backup",          'b', POPT_ARG_VAL,    &make_backups, 1, 0, 0 },
  {"no-backup",        0,  POPT_ARG_VAL,    &make_backups, 0, 0, 0 },
  {"backup-dir",       0,  POPT_ARG_STRING, &backup_dir, 0, 0, 0 },
  {"suffix",           0,  POPT_ARG_STRING, &backup_suffix, 0, 0, 0 },
  {"list-only",        0,  POPT_ARG_VAL,    &list_only, 2, 0, 0 },
  {"read-batch",       0,  POPT_ARG_STRING, &batch_name, OPT_READ_BATCH, 0, 0 },
  {"write-batch",      0,  POPT_ARG_STRING, &batch_name, OPT_WRITE_BATCH, 0, 0 },
  {"only-write-batch", 0,  POPT_ARG_STRING, &batch_name, OPT_ONLY_WRITE_BATCH, 0, 0 },
  {"files-from",       0,  POPT_ARG_STRING, &files_from, 0, 0, 0 },
  {"from0",           '0', POPT_ARG_VAL,    &eol_nulls, 1, 0, 0},
  {"no-from0",         0,  POPT_ARG_VAL,    &eol_nulls, 0, 0, 0},
  {"old-args",         0,  POPT_ARG_NONE,   0, OPT_OLD_ARGS, 0, 0},
  {"no-old-args",      0,  POPT_ARG_VAL,    &old_style_args, 0, 0, 0},
  {"secluded-args",   's', POPT_ARG_VAL,    &protect_args, 1, 0, 0},
  {"no-secluded-args", 0,  POPT_ARG_VAL,    &protect_args, 0, 0, 0},
  {"protect-args",     0,  POPT_ARG_VAL,    &protect_args, 1, 0, 0},
  {"no-protect-args",  0,  POPT_ARG_VAL,    &protect_args, 0, 0, 0},
  {"no-s",             0,  POPT_ARG_VAL,    &protect_args, 0, 0, 0},
  {"trust-sender",     0,  POPT_ARG_VAL,    &trust_sender, 1, 0, 0},
  {"numeric-ids",      0,  POPT_ARG_VAL,    &numeric_ids, 1, 0, 0 },
  {"no-numeric-ids",   0,  POPT_ARG_VAL,    &numeric_ids, 0, 0, 0 },
  {"usermap",          0,  POPT_ARG_STRING, 0, OPT_USERMAP, 0, 0 },
  {"groupmap",         0,  POPT_ARG_STRING, 0, OPT_GROUPMAP, 0, 0 },
  {"chown",            0,  POPT_ARG_STRING, 0, OPT_CHOWN, 0, 0 },
  {"timeout",          0,  POPT_ARG_INT,    &io_timeout, 0, 0, 0 },
  {"no-timeout",       0,  POPT_ARG_VAL,    &io_timeout, 0, 0, 0 },
  {"contimeout",       0,  POPT_ARG_INT,    &connect_timeout, 0, 0, 0 },
  {"no-contimeout",    0,  POPT_ARG_VAL,    &connect_timeout, 0, 0, 0 },
  {"fsync",            0,  POPT_ARG_NONE,   &do_fsync, 0, 0, 0 },
  {"stop-after",       0,  POPT_ARG_STRING, 0, OPT_STOP_AFTER, 0, 0 },
  {"time-limit",       0,  POPT_ARG_STRING, 0, OPT_STOP_AFTER, 0, 0 }, /* earlier stop-after name */
  {"stop-at",          0,  POPT_ARG_STRING, 0, OPT_STOP_AT, 0, 0 },
  {"rsh",             'e', POPT_ARG_STRING, &shell_cmd, 0, 0, 0 },
  {"rsync-path",       0,  POPT_ARG_STRING, &rsync_path, 0, 0, 0 },
  {"temp-dir",        'T', POPT_ARG_STRING, &tmpdir, 0, 0, 0 },
  {"iconv",            0,  POPT_ARG_STRING, &iconv_opt, 0, 0, 0 },
  {"no-iconv",         0,  POPT_ARG_NONE,   0, OPT_NO_ICONV, 0, 0 },
  {"ipv4",            '4', POPT_ARG_VAL,    &default_af_hint, AF_INET, 0, 0 },
  {"ipv6",            '6', POPT_ARG_VAL,    &default_af_hint, AF_INET6, 0, 0 },
  {"8-bit-output",    '8', POPT_ARG_VAL,    &allow_8bit_chars, 1, 0, 0 },
  {"no-8-bit-output",  0,  POPT_ARG_VAL,    &allow_8bit_chars, 0, 0, 0 },
  {"no-8",             0,  POPT_ARG_VAL,    &allow_8bit_chars, 0, 0, 0 },
  {"mkpath",           0,  POPT_ARG_VAL,    &mkpath_dest_arg, 1, 0, 0 },
  {"no-mkpath",        0,  POPT_ARG_VAL,    &mkpath_dest_arg, 0, 0, 0 },
  {"qsort",            0,  POPT_ARG_NONE,   &use_qsort, 0, 0, 0 },
  {"copy-as",          0,  POPT_ARG_STRING, &copy_as, 0, 0, 0 },
  {"address",          0,  POPT_ARG_STRING, &bind_address, 0, 0, 0 },
  {"port",             0,  POPT_ARG_INT,    &rsync_port, 0, 0, 0 },
  {"sockopts",         0,  POPT_ARG_STRING, &sockopts, 0, 0, 0 },
  {"password-file",    0,  POPT_ARG_STRING, &password_file, 0, 0, 0 },
  {"early-input",      0,  POPT_ARG_STRING, &early_input_file, 0, 0, 0 },
  {"blocking-io",      0,  POPT_ARG_VAL,    &blocking_io, 1, 0, 0 },
  {"no-blocking-io",   0,  POPT_ARG_VAL,    &blocking_io, 0, 0, 0 },
  {"outbuf",           0,  POPT_ARG_STRING, &outbuf_mode, 0, 0, 0 },
  {"remote-option",   'M', POPT_ARG_STRING, 0, 'M', 0, 0 },
  {"protocol",         0,  POPT_ARG_INT,    &protocol_version, 0, 0, 0 },
  {"checksum-seed",    0,  POPT_ARG_INT,    &checksum_seed, 0, 0, 0 },
  {"server",           0,  POPT_ARG_NONE,   0, OPT_SERVER, 0, 0 },
  {"sender",           0,  POPT_ARG_NONE,   0, OPT_SENDER, 0, 0 },
  /* All the following options switch us into daemon-mode option-parsing. */
  {"config",           0,  POPT_ARG_STRING, 0, OPT_DAEMON, 0, 0 },
  {"daemon",           0,  POPT_ARG_NONE,   0, OPT_DAEMON, 0, 0 },
  {"dparam",           0,  POPT_ARG_STRING, 0, OPT_DAEMON, 0, 0 },
  {"detach",           0,  POPT_ARG_NONE,   0, OPT_DAEMON, 0, 0 },
  {"no-detach",        0,  POPT_ARG_NONE,   0, OPT_DAEMON, 0, 0 },
  {0,0,0,0, 0, 0, 0}
};

static struct poptOption long_daemon_options[] = {
  /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
  {"address",          0,  POPT_ARG_STRING, &bind_address, 0, 0, 0 },
  {"bwlimit",          0,  POPT_ARG_INT,    &daemon_bwlimit, 0, 0, 0 },
  {"config",           0,  POPT_ARG_STRING, &config_file, 0, 0, 0 },
  {"daemon",           0,  POPT_ARG_NONE,   &daemon_opt, 0, 0, 0 },
  {"dparam",          'M', POPT_ARG_STRING, 0, 'M', 0, 0 },
  {"ipv4",            '4', POPT_ARG_VAL,    &default_af_hint, AF_INET, 0, 0 },
  {"ipv6",            '6', POPT_ARG_VAL,    &default_af_hint, AF_INET6, 0, 0 },
  {"detach",           0,  POPT_ARG_VAL,    &no_detach, 0, 0, 0 },
  {"no-detach",        0,  POPT_ARG_VAL,    &no_detach, 1, 0, 0 },
  {"log-file",         0,  POPT_ARG_STRING, &logfile_name, 0, 0, 0 },
  {"log-file-format",  0,  POPT_ARG_STRING, &logfile_format, 0, 0, 0 },
  {"port",             0,  POPT_ARG_INT,    &rsync_port, 0, 0, 0 },
  {"sockopts",         0,  POPT_ARG_STRING, &sockopts, 0, 0, 0 },
  {"protocol",         0,  POPT_ARG_INT,    &protocol_version, 0, 0, 0 },
  {"server",           0,  POPT_ARG_NONE,   &am_server, 0, 0, 0 },
  {"temp-dir",        'T', POPT_ARG_STRING, &tmpdir, 0, 0, 0 },
  {"verbose",         'v', POPT_ARG_NONE,   0, 'v', 0, 0 },
  {"no-verbose",       0,  POPT_ARG_VAL,    &verbose, 0, 0, 0 },
  {"no-v",             0,  POPT_ARG_VAL,    &verbose, 0, 0, 0 },
  {"help",            'h', POPT_ARG_NONE,   0, 'h', 0, 0 },
  {0,0,0,0, 0, 0, 0}
};


static char err_buf[200];


/**
 * Store the option error message, if any, so that we can log the
 * connection attempt (which requires parsing the options), and then
 * show the error later on.
 **/
void option_error(void)
{
	if (!err_buf[0]) {
		strlcpy(err_buf, "Error parsing options: option may "
			"be supported on client but not on server?\n",
			sizeof err_buf);
	}

	rprintf(FERROR, RSYNC_NAME ": %s", err_buf);
	io_flush(MSG_FLUSH);
	msleep(20);
}


static void parse_one_refuse_match(int negated, const char *ref, const struct poptOption *list_end)
{
	struct poptOption *op;
	char shortName[2];
	int is_wild = strpbrk(ref, "*?[") != NULL;
	int found_match = 0;

	shortName[1] = '\0';

	if (strcmp("a", ref) == 0 || strcmp("archive", ref) == 0) {
		ref = "[ardlptgoD]";
		is_wild = 1;
	}

	for (op = long_options; op != list_end; op++) {
		*shortName = op->shortName;
		if ((op->longName && wildmatch(ref, op->longName))
		 || (*shortName && wildmatch(ref, shortName))) {
			if (op->descrip[1] == '*')
				op->descrip = negated ? "a*" : "r*";
			else if (!is_wild)
				op->descrip = negated ? "a=" : "r=";
			found_match = 1;
			if (!is_wild)
				break;
		}
	}

	if (!found_match)
		rprintf(FLOG, "No match for refuse-options string \"%s\"\n", ref);
}


/**
 * Tweak the option table to disable all options that the rsyncd.conf
 * file has told us to refuse.
 **/
static void set_refuse_options(void)
{
	struct poptOption *op, *list_end = NULL;
	char *cp, *ref = lp_refuse_options(module_id);
	int negated;

	if (!ref)
		ref = "";

	if (!am_daemon)
		ref = "";

	/* We abuse the descrip field in poptOption to make it easy to flag which options
	 * are refused (since we don't use it otherwise).  Start by marking all options
	 * as "a"ccepted with a few options also marked as non-wild. */
	for (op = long_options; ; op++) {
		const char *longName = op->longName ? op->longName : "";
		if (!op->longName && !op->shortName) {
			list_end = op;
			break;
		}
		if (!am_daemon
		 || op->shortName == 'e' /* Required for compatibility flags */
		 || op->shortName == '0' /* --from0 just modifies --files-from, so refuse that instead (or not) */
		 || op->shortName == 's' /* --secluded-args is always OK */
		 || op->shortName == 'n' /* --dry-run is always OK */
		 || strcmp("iconv", longName) == 0
		 || strcmp("no-iconv", longName) == 0
		 || strcmp("checksum-seed", longName) == 0
		 || strcmp("copy-devices", longName) == 0 /* disable wild-match (it gets refused below) */
		 || strcmp("write-devices", longName) == 0 /* disable wild-match (it gets refused below) */
		 || strcmp("log-format", longName) == 0 /* aka out-format (NOT log-file-format) */
		 || strcmp("sender", longName) == 0
		 || strcmp("server", longName) == 0)
			op->descrip = "a="; /* exact-match only */
		else
			op->descrip = "a*"; /* wild-card-able */
	}
	assert(list_end != NULL);

	if (am_daemon) { /* Refused by default, but can be accepted via a negated exact match. */
		parse_one_refuse_match(0, "copy-devices", list_end);
		parse_one_refuse_match(0, "write-devices", list_end);
	}

	while (1) {
		while (*ref == ' ') ref++;
		if (!*ref)
			break;
		if ((cp = strchr(ref, ' ')) != NULL)
			*cp = '\0';
		negated = *ref == '!';
		if (negated && ref[1])
			ref++;
		parse_one_refuse_match(negated, ref, list_end);
		if (!cp)
			break;
		*cp = ' ';
		ref = cp + 1;
	}

	if (am_daemon) {
#ifdef ICONV_OPTION
		if (!*lp_charset(module_id))
			parse_one_refuse_match(0, "iconv", list_end);
#endif
		parse_one_refuse_match(0, "log-file*", list_end);
	}

#ifndef SUPPORT_ATIMES
	parse_one_refuse_match(0, "atimes", list_end);
#endif
#ifndef SUPPORT_HARD_LINKS
	parse_one_refuse_match(0, "link-dest", list_end);
#endif
#ifndef HAVE_MKTIME
	parse_one_refuse_match(0, "stop-at", list_end);
#endif
#ifndef ICONV_OPTION
	parse_one_refuse_match(0, "iconv", list_end);
#endif
#ifndef HAVE_SETVBUF
	parse_one_refuse_match(0, "outbuf", list_end);
#endif
#ifndef SUPPORT_CRTIMES
	parse_one_refuse_match(0, "crtimes", list_end);
#endif

	/* Now we use the descrip values to actually mark the options for refusal. */
	for (op = long_options; op != list_end; op++) {
		int refused = op->descrip[0] == 'r';
		op->descrip = NULL;
		if (!refused)
			continue;
		if (op->argInfo == POPT_ARG_VAL)
			op->argInfo = POPT_ARG_NONE;
		op->val = (op - long_options) + OPT_REFUSED_BASE;
		/* The following flags are set to let us easily check an implied option later in the code. */
		switch (op->shortName) {
		case 'r': case 'd': case 'l': case 'p':
		case 't': case 'g': case 'o': case 'D':
			refused_archive_part = op->val;
			break;
		case 'z':
			refused_compress = op->val;
			break;
		case '\0':
			if (strcmp("delete", op->longName) == 0)
				refused_delete = op->val;
			else if (strcmp("delete-before", op->longName) == 0)
				refused_delete_before = op->val;
			else if (strcmp("delete-during", op->longName) == 0)
				refused_delete_during = op->val;
			else if (strcmp("partial", op->longName) == 0)
				refused_partial = op->val;
			else if (strcmp("progress", op->longName) == 0)
				refused_progress = op->val;
			else if (strcmp("inplace", op->longName) == 0)
				refused_inplace = op->val;
			else if (strcmp("no-iconv", op->longName) == 0)
				refused_no_iconv = op->val;
			break;
		}
	}
}


static int count_args(const char **argv)
{
	int i = 0;

	if (argv) {
		while (argv[i] != NULL)
			i++;
	}

	return i;
}

/* If the size_arg is an invalid string or the value is < min_value, an error
 * is put into err_buf & the return is -1.  Note that this parser does NOT
 * support negative numbers, so a min_value < 0 doesn't make any sense. */
static ssize_t parse_size_arg(const char *size_arg, char def_suf, const char *opt_name,
			      ssize_t min_value, ssize_t max_value, BOOL unlimited_0)
{
	int reps, mult, len;
	const char *arg, *err = "invalid", *min_max = NULL;
	ssize_t limit = -1, size = 1;

	for (arg = size_arg; isDigit(arg); arg++) {}
	if (*arg == '.' || *arg == get_decimal_point()) /* backward compatibility: always allow '.' */
		for (arg++; isDigit(arg); arg++) {}
	switch (*arg && *arg != '+' && *arg != '-' ? *arg++ : def_suf) {
	case 'b': case 'B':
		reps = 0;
		break;
	case 'k': case 'K':
		reps = 1;
		break;
	case 'm': case 'M':
		reps = 2;
		break;
	case 'g': case 'G':
		reps = 3;
		break;
	case 't': case 'T':
		reps = 4;
		break;
	case 'p': case 'P':
		reps = 5;
		break;
	default:
		goto failure;
	}
	if (*arg == 'b' || *arg == 'B')
		mult = 1000, arg++;
	else if (!*arg || *arg == '+' || *arg == '-')
		mult = 1024;
	else if (strncasecmp(arg, "ib", 2) == 0)
		mult = 1024, arg += 2;
	else
		goto failure;
	while (reps--)
		size *= mult;
	size *= atof(size_arg);
	if ((*arg == '+' || *arg == '-') && arg[1] == '1' && arg != size_arg)
		size += atoi(arg), arg += 2;
	if (*arg)
		goto failure;
	if (size < 0 || (max_value >= 0 && size > max_value)) {
		err = "too large";
		min_max = "max";
		limit = max_value;
		goto failure;
	}
	if (size < min_value && (!unlimited_0 || size != 0)) {
		err = "too small";
		min_max = "min";
		limit = min_value;
		goto failure;
	}
	return size;

failure:
	len = snprintf(err_buf, sizeof err_buf - 1, "--%s=%s is %s", opt_name, size_arg, err);
	if (min_max && limit >= 0 && len < (int)sizeof err_buf - 10) {
		len += snprintf(err_buf + len, sizeof err_buf - len - 1, " (%s: %s%s)",
			min_max, do_big_num(limit, 3, NULL),
			unlimited_0 && min_max[1] == 'i' ? " or 0 for unlimited" : "");
	}
	err_buf[len] = '\n';
	err_buf[len+1] = '\0';
	return -1;
}

#ifdef HAVE_MKTIME
/* Allow the user to specify a time in the format yyyy-mm-ddThh:mm while
 * also allowing abbreviated data.  For instance, if the time is omitted,
 * it defaults to midnight.  If the date is omitted, it defaults to the
 * next possible date in the future with the specified time.  Even the
 * year or year-month can be omitted, again defaulting to the next date
 * in the future that matches the specified information.  A 2-digit year
 * is also OK, as is using '/' instead of '-'. */
static time_t parse_time(const char *arg)
{
	const char *cp;
	time_t val, now = time(NULL);
	struct tm t, *today = localtime(&now);
	int in_date, old_mday, n;

	memset(&t, 0, sizeof t);
	t.tm_year = t.tm_mon = t.tm_mday = -1;
	t.tm_hour = t.tm_min = t.tm_isdst = -1;
	cp = arg;
	if (*cp == 'T' || *cp == 't' || *cp == ':') {
		in_date = *cp == ':' ? 0 : -1;
		cp++;
	} else
		in_date = 1;
	for ( ; ; cp++) {
		if (!isDigit(cp))
			return (time_t)-1;
		n = 0;
		do {
			n = n * 10 + *cp++ - '0';
		} while (isDigit(cp));
		if (*cp == ':')
			in_date = 0;
		if (in_date > 0) {
			if (t.tm_year != -1)
				return (time_t)-1;
			t.tm_year = t.tm_mon;
			t.tm_mon = t.tm_mday;
			t.tm_mday = n;
			if (!*cp)
				break;
			if (*cp == 'T' || *cp == 't') {
				if (!cp[1])
					break;
				in_date = -1;
			} else if (*cp != '-' && *cp != '/')
				return (time_t)-1;
			continue;
		}
		if (t.tm_hour != -1)
			return (time_t)-1;
		t.tm_hour = t.tm_min;
		t.tm_min = n;
		if (!*cp) {
			if (in_date < 0)
				return (time_t)-1;
			break;
		}
		if (*cp != ':')
			return (time_t)-1;
		in_date = 0;
	}

	in_date = 0;
	if (t.tm_year < 0) {
		t.tm_year = today->tm_year;
		in_date = 1;
	} else if (t.tm_year < 100) {
		while (t.tm_year < today->tm_year)
			t.tm_year += 100;
	} else
		t.tm_year -= 1900;
	if (t.tm_mon < 0) {
		t.tm_mon = today->tm_mon;
		in_date = 2;
	} else
		t.tm_mon--;
	if (t.tm_mday < 0) {
		t.tm_mday = today->tm_mday;
		in_date = 3;
	}

	n = 0;
	if (t.tm_min < 0) {
		t.tm_hour = t.tm_min = 0;
	} else if (t.tm_hour < 0) {
		if (in_date != 3)
			return (time_t)-1;
		in_date = 0;
		t.tm_hour = today->tm_hour;
		n = 60*60;
	}

	/* Note that mktime() might change a too-large tm_mday into the start of
	 * the following month which we need to undo in the following code! */
	old_mday = t.tm_mday;
	if (t.tm_hour > 23 || t.tm_min > 59
	    || t.tm_mon < 0 || t.tm_mon >= 12
	    || t.tm_mday < 1 || t.tm_mday > 31
	    || (val = mktime(&t)) == (time_t)-1)
		return (time_t)-1;

	while (in_date && (val <= now || t.tm_mday < old_mday)) {
		switch (in_date) {
		case 3:
			old_mday = ++t.tm_mday;
			break;
		case 2:
			if (t.tm_mday < old_mday)
				t.tm_mday = old_mday; /* The month already got bumped forward */
			else if (++t.tm_mon == 12) {
				t.tm_mon = 0;
				t.tm_year++;
			}
			break;
		case 1:
			if (t.tm_mday < old_mday) {
				/* mon==1 mday==29 got bumped to mon==2 */
				if (t.tm_mon != 2 || old_mday != 29)
					return (time_t)-1;
				t.tm_mon = 1;
				t.tm_mday = 29;
			}
			t.tm_year++;
			break;
		}
		if ((val = mktime(&t)) == (time_t)-1) {
			/* This code shouldn't be needed, as mktime() should auto-round to the next month. */
			if (in_date != 3 || t.tm_mday <= 28)
				return (time_t)-1;
			t.tm_mday = old_mday = 1;
			in_date = 2;
		}
	}
	if (n) {
		while (val <= now)
			val += n;
	}
	return val;
}
#endif

static void create_refuse_error(int which)
{
	const char *msg;
	if (am_daemon)
		msg = "The server is configured to refuse";
	else if (am_server)
		msg = "The server does not support";
	else
		msg = "This rsync does not support";

	/* The "which" value is the index + OPT_REFUSED_BASE. */
	struct poptOption *op = &long_options[which - OPT_REFUSED_BASE];
	int n = snprintf(err_buf, sizeof err_buf, "%s --%s\n", msg, op->longName) - 1;
	if (op->shortName)
		snprintf(err_buf + n, sizeof err_buf - n, " (-%c)\n", op->shortName);
}

/* This is used to make sure that --daemon & --server cannot be aliased to
 * something else. These options have always disabled popt aliases for the
 * parsing of a daemon or server command-line, but we have to make sure that
 * these options cannot vanish so that the alias disabling can take effect. */
static void popt_unalias(poptContext con, const char *opt)
{
	struct poptAlias unalias;

	memset(&unalias, 0, sizeof unalias);

	unalias.longName = opt + 2; /* point past the leading "--" */
	unalias.argc = 1;
	unalias.argv = new_array0(const char*, 2);
	unalias.argv[0] = strdup(opt);

	poptAddAlias(con, unalias, 0);
}

char *alt_dest_opt(int type)
{
	if (!type)
		type = alt_dest_type;

	switch (type) {
	case COMPARE_DEST:
		return "--compare-dest";
	case COPY_DEST:
		return "--copy-dest";
	case LINK_DEST:
		return "--link-dest";
	default:
		NOISY_DEATH("Unknown alt_dest_opt type");
	}
}

/**
 * Process command line arguments.  Called on both local and remote.
 *
 * @retval 1 if all options are OK; with globals set to appropriate
 * values
 *
 * @retval 0 on error, with err_buf containing an explanation
 **/
int parse_arguments(int *argc_p, const char ***argv_p)
{
	poptContext pc;
	const char *arg, **argv = *argv_p;
	int argc = *argc_p;
	int opt, want_dest_type;
	int orig_protect_args = protect_args;

	if (argc == 0) {
		strlcpy(err_buf, "argc is zero!\n", sizeof err_buf);
		return 0;
	}

	set_refuse_options();

#ifdef ICONV_OPTION
	if (!am_daemon && protect_args <= 0 && (arg = getenv("RSYNC_ICONV")) != NULL && *arg)
		iconv_opt = strdup(arg);
#endif

	/* TODO: Call poptReadDefaultConfig; handle errors. */

	pc = poptGetContext(RSYNC_NAME, argc, argv, long_options, 0);
	if (!am_server) {
		poptReadDefaultConfig(pc, 0);
		popt_unalias(pc, "--daemon");
		popt_unalias(pc, "--server");
	}

	while ((opt = poptGetNextOpt(pc)) != -1) {
		/* most options are handled automatically by popt;
		 * only special cases are returned and listed here. */

		switch (opt) {
		case 'V':
			version_opt_cnt++;
			break;

		case OPT_SERVER:
			if (!am_server) {
				/* Disable popt aliases on the server side and
				 * then start parsing the options again. */
				poptFreeContext(pc);
				pc = poptGetContext(RSYNC_NAME, argc, argv, long_options, 0);
				am_server = 1;
			}
#ifdef ICONV_OPTION
			iconv_opt = NULL;
#endif
			break;

		case OPT_SENDER:
			if (!am_server) {
				usage(FERROR);
				exit_cleanup(RERR_SYNTAX);
			}
			am_sender = 1;
			break;

		case OPT_DAEMON:
			if (am_daemon) {
				strlcpy(err_buf,
					"Attempt to hack rsync thwarted!\n",
					sizeof err_buf);
				goto cleanup;
			}
#ifdef ICONV_OPTION
			iconv_opt = NULL;
#endif
			protect_args = 0;
			poptFreeContext(pc);
			pc = poptGetContext(RSYNC_NAME, argc, argv, long_daemon_options, 0);
			while ((opt = poptGetNextOpt(pc)) != -1) {
				char **cpp;
				switch (opt) {
				case 'h':
					daemon_usage(FINFO);
					exit_cleanup(0);

				case 'M':
					arg = poptGetOptArg(pc);
					if (!strchr(arg, '=')) {
						rprintf(FERROR,
							"--dparam value is missing an '=': %s\n",
							arg);
						goto daemon_error;
					}
					cpp = EXPAND_ITEM_LIST(&dparam_list, char *, 4);
					*cpp = strdup(arg);
					break;

				case 'v':
					verbose++;
					break;

				default:
					rprintf(FERROR,
						"rsync: %s: %s (in daemon mode)\n",
						poptBadOption(pc, POPT_BADOPTION_NOALIAS),
						poptStrerror(opt));
					goto daemon_error;
				}
			}

			if (dparam_list.count && !set_dparams(1))
				exit_cleanup(RERR_SYNTAX);

			if (tmpdir && strlen(tmpdir) >= MAXPATHLEN - 10) {
				snprintf(err_buf, sizeof err_buf,
					 "the --temp-dir path is WAY too long.\n");
				goto cleanup;
			}

			if (!daemon_opt) {
				rprintf(FERROR, "Daemon option(s) used without --daemon.\n");
			    daemon_error:
				rprintf(FERROR, "(Type \"rsync --daemon --help\" for assistance with daemon mode.)\n");
				exit_cleanup(RERR_SYNTAX);
			}

			argv = poptGetArgs(pc);
			argc = count_args(argv);
			if (!argc) {
				*argv_p = empty_argv;
				*argc_p = 0;
			} else if (poptDupArgv(argc, argv, argc_p, argv_p) != 0)
				out_of_memory("parse_arguments");
			argv = *argv_p;
			poptFreeContext(pc);

			am_starting_up = 0;
			daemon_opt = 0;
			am_daemon = 1;
			return 1;

		case OPT_MODIFY_WINDOW:
			/* The value has already been set by popt, but
			 * we need to remember that we're using a
			 * non-default setting. */
			modify_window_set = 1;
			break;

		case OPT_FILTER:
			parse_filter_str(&filter_list, poptGetOptArg(pc),
					rule_template(0), 0);
			break;

		case OPT_EXCLUDE:
			parse_filter_str(&filter_list, poptGetOptArg(pc),
					rule_template(0), XFLG_OLD_PREFIXES);
			break;

		case OPT_INCLUDE:
			parse_filter_str(&filter_list, poptGetOptArg(pc),
					rule_template(FILTRULE_INCLUDE), XFLG_OLD_PREFIXES);
			break;

		case OPT_EXCLUDE_FROM:
		case OPT_INCLUDE_FROM:
			arg = poptGetOptArg(pc);
			if (sanitize_paths)
				arg = sanitize_path(NULL, arg, NULL, 0, SP_DEFAULT);
			if (daemon_filter_list.head) {
				int rej;
				char *cp = strdup(arg);
				if (!*cp)
					rej = 1;
				else {
					char *dir = cp + (*cp == '/' ? module_dirlen : 0);
					clean_fname(dir, CFN_COLLAPSE_DOT_DOT_DIRS);
					rej = check_filter(&daemon_filter_list, FLOG, dir, 0) < 0;
				}
				free(cp);
				if (rej)
					goto options_rejected;
			}
			parse_filter_file(&filter_list, arg,
				rule_template(opt == OPT_INCLUDE_FROM ? FILTRULE_INCLUDE : 0),
				XFLG_FATAL_ERRORS | XFLG_OLD_PREFIXES);
			break;

		case 'a':
			if (refused_archive_part) {
				create_refuse_error(refused_archive_part);
				goto cleanup;
			}
			if (!recurse) /* preserve recurse == 2 */
				recurse = 1;
#ifdef SUPPORT_LINKS
			preserve_links = 1;
#endif
			preserve_perms = 1;
			preserve_mtimes = 1;
			preserve_gid = 1;
			preserve_uid = 1;
			preserve_devices = 1;
			preserve_specials = 1;
			break;

		case 'D':
			preserve_devices = preserve_specials = 1;
			break;

		case OPT_NO_D:
			preserve_devices = preserve_specials = 0;
			break;

		case 'h':
			human_readable++;
			break;

		case 'H':
			preserve_hard_links++;
			break;

		case 'i':
			itemize_changes++;
			break;

		case 'U':
			if (++preserve_atimes > 1)
				open_noatime = 1;
			break;

		case 'v':
			verbose++;
			break;

		case 'y':
			fuzzy_basis++;
			break;

		case 'q':
			quiet++;
			break;

		case 'x':
			one_file_system++;
			break;

		case 'F':
			switch (++F_option_cnt) {
			case 1:
				parse_filter_str(&filter_list,": /.rsync-filter",rule_template(0),0);
				break;
			case 2:
				parse_filter_str(&filter_list,"- .rsync-filter",rule_template(0),0);
				break;
			}
			break;

		case 'P':
			if (refused_partial || refused_progress) {
				create_refuse_error(refused_partial ? refused_partial : refused_progress);
				goto cleanup;
			}
			do_progress = 1;
			keep_partial = 1;
			break;

		case 'z':
			do_compression++;
			break;

		case OPT_OLD_COMPRESS:
			compress_choice = "zlib";
			break;

		case OPT_NEW_COMPRESS:
			compress_choice = "zlibx";
			break;

		case OPT_NO_COMPRESS:
			do_compression = 0;
			compress_choice = NULL;
			break;

		case OPT_OLD_ARGS:
			if (old_style_args <= 0)
				old_style_args = 1;
			else
				old_style_args++;
			break;

		case 'M':
			arg = poptGetOptArg(pc);
			if (*arg != '-') {
				snprintf(err_buf, sizeof err_buf,
					"Remote option must start with a dash: %s\n", arg);
				goto cleanup;
			}
			if (remote_option_cnt+2 >= remote_option_alloc) {
				remote_option_alloc += 16;
				remote_options = realloc_array(remote_options,
							const char *, remote_option_alloc);
				if (!remote_option_cnt)
					remote_options[0] = "ARG0";
			}
			remote_options[++remote_option_cnt] = arg;
			remote_options[remote_option_cnt+1] = NULL;
			break;

		case OPT_WRITE_BATCH:
			/* batch_name is already set */
			write_batch = 1;
			break;

		case OPT_ONLY_WRITE_BATCH:
			/* batch_name is already set */
			write_batch = -1;
			break;

		case OPT_READ_BATCH:
			/* batch_name is already set */
			read_batch = 1;
			break;

		case OPT_NO_ICONV:
#ifdef ICONV_OPTION
			iconv_opt = NULL;
#endif
			break;

		case OPT_BLOCK_SIZE: {
			/* We may not know the real protocol_version at this point if this is the client
			 * option parsing, but we still want to check it so that the client can specify
			 * a --protocol=29 option with a larger block size. */
			int max_blength = protocol_version < 30 ? OLD_MAX_BLOCK_SIZE : MAX_BLOCK_SIZE;
			ssize_t size;
			arg = poptGetOptArg(pc);
			if ((size = parse_size_arg(arg, 'b', "block-size", 0, max_blength, False)) < 0)
				goto cleanup;
			block_size = (int32)size;
			break;
		}

		case OPT_MAX_SIZE:
			if ((max_size = parse_size_arg(max_size_arg, 'b', "max-size", 0, -1, False)) < 0)
				goto cleanup;
			max_size_arg = strdup(do_big_num(max_size, 0, NULL));
			break;

		case OPT_MIN_SIZE:
			if ((min_size = parse_size_arg(min_size_arg, 'b', "min-size", 0, -1, False)) < 0)
				goto cleanup;
			min_size_arg = strdup(do_big_num(min_size, 0, NULL));
			break;

		case OPT_BWLIMIT: {
			ssize_t size = parse_size_arg(bwlimit_arg, 'K', "bwlimit", 512, -1, True);
			if (size < 0)
				goto cleanup;
			bwlimit_arg = strdup(do_big_num(size, 0, NULL));
			bwlimit = (size + 512) / 1024;
			break;
		}

		case OPT_APPEND:
			if (am_server)
				append_mode++;
			else
				append_mode = 1;
			break;

		case OPT_LINK_DEST:
			want_dest_type = LINK_DEST;
			goto set_dest_dir;

		case OPT_COPY_DEST:
			want_dest_type = COPY_DEST;
			goto set_dest_dir;

		case OPT_COMPARE_DEST:
			want_dest_type = COMPARE_DEST;

		set_dest_dir:
			if (alt_dest_type && alt_dest_type != want_dest_type) {
				snprintf(err_buf, sizeof err_buf,
					"ERROR: the %s option conflicts with the %s option\n",
					alt_dest_opt(want_dest_type), alt_dest_opt(0));
				goto cleanup;
			}
			alt_dest_type = want_dest_type;

			if (basis_dir_cnt >= MAX_BASIS_DIRS) {
				snprintf(err_buf, sizeof err_buf,
					"ERROR: at most %d %s args may be specified\n",
					MAX_BASIS_DIRS, alt_dest_opt(0));
				goto cleanup;
			}
			/* We defer sanitizing this arg until we know what
			 * our destination directory is going to be. */
			basis_dir[basis_dir_cnt++] = (char *)poptGetOptArg(pc);
			break;

		case OPT_CHMOD:
			arg = poptGetOptArg(pc);
			if (!parse_chmod(arg, &chmod_modes)) {
				snprintf(err_buf, sizeof err_buf,
					"Invalid argument passed to --chmod (%s)\n",
					arg);
				goto cleanup;
			}
			break;

		case OPT_INFO:
			arg = poptGetOptArg(pc);
			parse_output_words(info_words, info_levels, arg, USER_PRIORITY);
			break;

		case OPT_DEBUG:
			arg = poptGetOptArg(pc);
			parse_output_words(debug_words, debug_levels, arg, USER_PRIORITY);
			break;

		case OPT_USERMAP:
			if (usermap) {
				if (usermap_via_chown) {
					snprintf(err_buf, sizeof err_buf,
						"--usermap conflicts with prior --chown.\n");
					goto cleanup;
				}
				snprintf(err_buf, sizeof err_buf,
					"You can only specify --usermap once.\n");
				goto cleanup;
			}
			usermap = (char *)poptGetOptArg(pc);
			usermap_via_chown = False;
			preserve_uid = 1;
			break;

		case OPT_GROUPMAP:
			if (groupmap) {
				if (groupmap_via_chown) {
					snprintf(err_buf, sizeof err_buf,
						"--groupmap conflicts with prior --chown.\n");
					goto cleanup;
				}
				snprintf(err_buf, sizeof err_buf,
					"You can only specify --groupmap once.\n");
				goto cleanup;
			}
			groupmap = (char *)poptGetOptArg(pc);
			groupmap_via_chown = False;
			preserve_gid = 1;
			break;

		case OPT_CHOWN: {
			const char *chown = poptGetOptArg(pc);
			int len;
			if ((arg = strchr(chown, ':')) != NULL)
				len = arg++ - chown;
			else
				len = strlen(chown);
			if (len) {
				if (usermap) {
					if (!usermap_via_chown) {
						snprintf(err_buf, sizeof err_buf,
							"--chown conflicts with prior --usermap.\n");
						goto cleanup;
					}
					snprintf(err_buf, sizeof err_buf,
						"You can only specify a user-affecting --chown once.\n");
					goto cleanup;
				}
				if (asprintf(&usermap, "*:%.*s", len, chown) < 0)
					out_of_memory("parse_arguments");
				usermap_via_chown = True;
				preserve_uid = 1;
			}
			if (arg && *arg) {
				if (groupmap) {
					if (!groupmap_via_chown) {
						snprintf(err_buf, sizeof err_buf,
							"--chown conflicts with prior --groupmap.\n");
						goto cleanup;
					}
					snprintf(err_buf, sizeof err_buf,
						"You can only specify a group-affecting --chown once.\n");
					goto cleanup;
				}
				if (asprintf(&groupmap, "*:%s", arg) < 0)
					out_of_memory("parse_arguments");
				groupmap_via_chown = True;
				preserve_gid = 1;
			}
			break;
		}

		case OPT_HELP:
			usage(FINFO);
			exit_cleanup(0);

		case 'A':
#ifdef SUPPORT_ACLS
			preserve_acls = 1;
			preserve_perms = 1;
			break;
#else
			/* FIXME: this should probably be ignored with a
			 * warning and then countermeasures taken to
			 * restrict group and other access in the presence
			 * of any more restrictive ACLs, but this is safe
			 * for now */
			snprintf(err_buf,sizeof(err_buf),
				 "ACLs are not supported on this %s\n",
				 am_server ? "server" : "client");
			goto cleanup;
#endif

		case 'X':
#ifdef SUPPORT_XATTRS
			preserve_xattrs++;
			break;
#else
			snprintf(err_buf,sizeof(err_buf),
				 "extended attributes are not supported on this %s\n",
				 am_server ? "server" : "client");
			goto cleanup;
#endif

		case OPT_STOP_AFTER: {
			long val;
			arg = poptGetOptArg(pc);
			stop_at_utime = time(NULL);
			if ((val = atol(arg) * 60) <= 0 || LONG_MAX - val < stop_at_utime || (long)(time_t)val != val) {
				snprintf(err_buf, sizeof err_buf, "invalid --stop-after value: %s\n", arg);
				goto cleanup;
			}
			stop_at_utime += val;
			break;
		}

#ifdef HAVE_MKTIME
		case OPT_STOP_AT:
			arg = poptGetOptArg(pc);
			if ((stop_at_utime = parse_time(arg)) == (time_t)-1) {
				snprintf(err_buf, sizeof err_buf, "invalid --stop-at format: %s\n", arg);
				goto cleanup;
			}
			if (stop_at_utime <= time(NULL)) {
				snprintf(err_buf, sizeof err_buf, "--stop-at time is not in the future: %s\n", arg);
				goto cleanup;
			}
			break;
#endif

		case OPT_STDERR: {
			int len;
			arg = poptGetOptArg(pc);
			len = strlen(arg);
			if (len && strncmp("errors", arg, len) == 0)
				msgs2stderr = 2;
			else if (len && strncmp("all", arg, len) == 0)
				msgs2stderr = 1;
			else if (len && strncmp("client", arg, len) == 0)
				msgs2stderr = 0;
			else {
				snprintf(err_buf, sizeof err_buf,
					"--stderr mode \"%s\" is not one of errors, all, or client\n", arg);
				goto cleanup;
			}
			saw_stderr_opt = 1;
			break;
		}

		default:
			/* A large opt value means that set_refuse_options()
			 * turned this option off. */
			if (opt >= OPT_REFUSED_BASE) {
				create_refuse_error(opt);
				goto cleanup;
			}
			snprintf(err_buf, sizeof err_buf, "%s%s: %s\n",
				 am_server ? "on remote machine: " : "",
				 poptBadOption(pc, POPT_BADOPTION_NOALIAS),
				 poptStrerror(opt));
			goto cleanup;
		}
	}

	if (msgs2stderr != 2)
		saw_stderr_opt = 1;

	if (version_opt_cnt) {
		print_rsync_version(version_opt_cnt > 1 && !am_server ? FNONE : FINFO);
		exit_cleanup(0);
	}

	if (!max_alloc_arg) {
		max_alloc_arg = getenv("RSYNC_MAX_ALLOC");
		if (max_alloc_arg && !*max_alloc_arg)
			max_alloc_arg = NULL;
	}
	if (max_alloc_arg) {
		ssize_t size = parse_size_arg(max_alloc_arg, 'B', "max-alloc", 1024*1024, -1, True);
		if (size < 0)
			goto cleanup;
		max_alloc = size;
	}
	if (!max_alloc)
		max_alloc = SIZE_MAX;

	if (old_style_args < 0) {
		if (!am_server && protect_args <= 0 && (arg = getenv("RSYNC_OLD_ARGS")) != NULL && *arg) {
			protect_args = 0;
			old_style_args = atoi(arg);
		} else
			old_style_args = 0;
	} else if (old_style_args) {
		if (protect_args > 0) {
			snprintf(err_buf, sizeof err_buf,
				 "--secluded-args conflicts with --old-args.\n");
			goto cleanup;
		}
		protect_args = 0;
	}

	if (protect_args < 0) {
		if (am_server)
			protect_args = 0;
		else if ((arg = getenv("RSYNC_PROTECT_ARGS")) != NULL && *arg)
			protect_args = atoi(arg) ? 1 : 0;
		else {
#ifdef RSYNC_USE_SECLUDED_ARGS
			protect_args = 1;
#else
			protect_args = 0;
#endif
		}
	}

	if (checksum_choice && strcasecmp(checksum_choice, "auto") != 0 && strcasecmp(checksum_choice, "auto,auto") != 0) {
		/* Call this early to verify the args and figure out if we need to force
		 * --whole-file. Note that the parse function will get called again later,
		 * just in case an "auto" choice needs to know the protocol_version. */
		parse_checksum_choice(0);
	} else
		checksum_choice = NULL;

	if (human_readable > 1 && argc == 2 && !am_server) {
		/* Allow the old meaning of 'h' (--help) on its own. */
		usage(FINFO);
		exit_cleanup(0);
	}

	if (!compress_choice && do_compression > 1)
		compress_choice = "zlibx";
	if (compress_choice && strcasecmp(compress_choice, "auto") != 0)
		parse_compress_choice(0); /* Twiddles do_compression and can possibly NULL-out compress_choice. */
	else
		compress_choice = NULL;

	if (do_compression || do_compression_level != CLVL_NOT_SPECIFIED) {
		if (!do_compression)
			do_compression = CPRES_AUTO;
		if (do_compression && refused_compress) {
			create_refuse_error(refused_compress);
			goto cleanup;
		}
	}

#ifdef HAVE_SETVBUF
	if (outbuf_mode && !am_server) {
		int mode = *(uchar *)outbuf_mode;
		if (islower(mode))
			mode = toupper(mode);
		fflush(stdout); /* Just in case... */
		switch (mode) {
		case 'N': /* None */
		case 'U': /* Unbuffered */
			mode = _IONBF;
			break;
		case 'L': /* Line */
			mode = _IOLBF;
			break;
		case 'B': /* Block */
		case 'F': /* Full */
			mode = _IOFBF;
			break;
		default:
			snprintf(err_buf, sizeof err_buf,
				"Invalid --outbuf setting -- specify N, L, or B.\n");
			goto cleanup;
		}
		setvbuf(stdout, (char *)NULL, mode, 0);
	}

	if (msgs2stderr == 1) { /* Are all messages going to stderr? */
		/* Make stderr line buffered for better sharing of the stream. */
		fflush(stderr); /* Just in case... */
		setvbuf(stderr, (char *)NULL, _IOLBF, 0);
	}
#endif

	set_output_verbosity(verbose, DEFAULT_PRIORITY);

	if (do_stats) {
		parse_output_words(info_words, info_levels,
			verbose > 1 ? "stats3" : "stats2", DEFAULT_PRIORITY);
	}

#ifdef ICONV_OPTION
	if (iconv_opt && protect_args != 2) {
		if (!am_server && strcmp(iconv_opt, "-") == 0)
			iconv_opt = NULL;
		else
			need_unsorted_flist = 1;
	}
	if (refused_no_iconv && !iconv_opt) {
		create_refuse_error(refused_no_iconv);
		goto cleanup;
	}
#endif

	if (fuzzy_basis > 1)
		fuzzy_basis = basis_dir_cnt + 1;

	/* Don't let the client reset protect_args if it was already processed */
	if (orig_protect_args == 2 && am_server)
		protect_args = orig_protect_args;

	if (protect_args == 1 && am_server) {
		poptFreeContext(pc);
		return 1;
	}

	/* Because popt 1.19 has started to free the returned args data, we now
	 * make a copy of the array and then do an immediate cleanup. */
	argv = poptGetArgs(pc);
	argc = count_args(argv);
	if (!argc) {
		*argv_p = empty_argv;
		*argc_p = 0;
	} else if (poptDupArgv(argc, argv, argc_p, argv_p) != 0)
		out_of_memory("parse_arguments");
	argv = *argv_p;
	poptFreeContext(pc);
	pc = NULL;

#ifndef SUPPORT_LINKS
	if (preserve_links && !am_sender) {
		snprintf(err_buf, sizeof err_buf,
			 "symlinks are not supported on this %s\n",
			 am_server ? "server" : "client");
		goto cleanup;
	}
#endif

#ifndef SUPPORT_HARD_LINKS
	if (preserve_hard_links) {
		snprintf(err_buf, sizeof err_buf,
			 "hard links are not supported on this %s\n",
			 am_server ? "server" : "client");
		goto cleanup;
	}
#endif

#ifdef SUPPORT_XATTRS
	if (am_root < 0 && preserve_xattrs > 1) {
		snprintf(err_buf, sizeof err_buf,
			 "--fake-super conflicts with -XX\n");
		goto cleanup;
	}
#else
	if (am_root < 0) {
		snprintf(err_buf, sizeof err_buf,
			 "--fake-super requires an rsync with extended attributes enabled\n");
		goto cleanup;
	}
#endif

	if (write_batch && read_batch) {
		snprintf(err_buf, sizeof err_buf,
			"--write-batch and --read-batch can not be used together\n");
		goto cleanup;
	}
	if (write_batch > 0 || read_batch) {
		if (am_server) {
			rprintf(FINFO,
				"ignoring --%s-batch option sent to server\n",
				write_batch ? "write" : "read");
			/* We don't actually exit_cleanup(), so that we can
			 * still service older version clients that still send
			 * batch args to server. */
			read_batch = write_batch = 0;
			batch_name = NULL;
		} else if (dry_run)
			write_batch = 0;
	} else if (write_batch < 0 && dry_run)
		write_batch = 0;
	if (read_batch && files_from) {
		snprintf(err_buf, sizeof err_buf,
			"--read-batch cannot be used with --files-from\n");
		goto cleanup;
	}
	if (read_batch && remove_source_files) {
		snprintf(err_buf, sizeof err_buf,
			"--read-batch cannot be used with --remove-%s-files\n",
			remove_source_files == 1 ? "source" : "sent");
		goto cleanup;
	}
	if (batch_name && strlen(batch_name) > MAX_BATCH_NAME_LEN) {
		snprintf(err_buf, sizeof err_buf,
			"the batch-file name must be %d characters or less.\n",
			MAX_BATCH_NAME_LEN);
		goto cleanup;
	}

	if (tmpdir && strlen(tmpdir) >= MAXPATHLEN - 10) {
		snprintf(err_buf, sizeof err_buf,
			 "the --temp-dir path is WAY too long.\n");
		goto cleanup;
	}

	if (max_delete < 0 && max_delete != INT_MIN) {
		/* Negative numbers are treated as "no deletions". */
		max_delete = 0;
	}

	if (files_from) {
		if (recurse == 1) /* preserve recurse == 2 */
			recurse = 0;
		if (xfer_dirs < 0)
			xfer_dirs = 1;
	}

	if (argc < 2 && !read_batch && !am_server)
		list_only |= 1;

	if (xfer_dirs >= 4) {
		parse_filter_str(&filter_list, "- /*/*", rule_template(0), 0);
		recurse = xfer_dirs = 1;
	} else if (recurse)
		xfer_dirs = 1;
	else if (xfer_dirs < 0)
		xfer_dirs = list_only ? 1 : 0;

	if (relative_paths < 0)
		relative_paths = files_from? 1 : 0;
	if (!relative_paths)
		implied_dirs = 0;

	if (delete_before + !!delete_during + delete_after > 1) {
		snprintf(err_buf, sizeof err_buf,
			"You may not combine multiple --delete-WHEN options.\n");
		goto cleanup;
	}
	if (delete_before || delete_during || delete_after)
		delete_mode = 1;
	else if (delete_mode || delete_excluded) {
		/* Only choose now between before & during if one is refused. */
		if (refused_delete_before) {
			if (!refused_delete_during)
				delete_during = 1;
			else {
				create_refuse_error(refused_delete_before);
				goto cleanup;
			}
		} else if (refused_delete_during)
			delete_before = 1;
		delete_mode = 1;
	}
	if (!xfer_dirs && delete_mode) {
		snprintf(err_buf, sizeof err_buf,
			"--delete does not work without --recursive (-r) or --dirs (-d).\n");
		goto cleanup;
	}

	if (missing_args == 3) /* simplify if both options were specified */
		missing_args = 2;
	if (refused_delete && (delete_mode || missing_args == 2)) {
		create_refuse_error(refused_delete);
		goto cleanup;
	}

	if (remove_source_files) {
		/* We only want to infer this refusal of --remove-source-files
		 * via the refusal of "delete", not any of the "delete-FOO"
		 * options. */
		if (refused_delete && am_sender) {
			create_refuse_error(refused_delete);
			goto cleanup;
		}
		need_messages_from_generator = 1;
	}

	if (munge_symlinks && !am_daemon) {
		STRUCT_STAT st;
		char prefix[SYMLINK_PREFIX_LEN]; /* NOT +1 ! */
		strlcpy(prefix, SYMLINK_PREFIX, sizeof prefix); /* trim the trailing slash */
		if (do_stat(prefix, &st) == 0 && S_ISDIR(st.st_mode)) {
			rprintf(FERROR, "Symlink munging is unsafe when a %s directory exists.\n",
				prefix);
			exit_cleanup(RERR_UNSUPPORTED);
		}
	}

	if (sanitize_paths) {
		int i;
		for (i = argc; i-- > 0; )
			argv[i] = sanitize_path(NULL, argv[i], "", 0, SP_KEEP_DOT_DIRS);
		if (tmpdir)
			tmpdir = sanitize_path(NULL, tmpdir, NULL, 0, SP_DEFAULT);
		if (backup_dir)
			backup_dir = sanitize_path(NULL, backup_dir, NULL, 0, SP_DEFAULT);
	}
	if (daemon_filter_list.head && !am_sender) {
		filter_rule_list *elp = &daemon_filter_list;
		if (tmpdir) {
			char *dir;
			if (!*tmpdir)
				goto options_rejected;
			dir = tmpdir + (*tmpdir == '/' ? module_dirlen : 0);
			clean_fname(dir, CFN_COLLAPSE_DOT_DOT_DIRS);
			if (check_filter(elp, FLOG, dir, 1) < 0)
				goto options_rejected;
		}
		if (backup_dir) {
			char *dir;
			if (!*backup_dir)
				goto options_rejected;
			dir = backup_dir + (*backup_dir == '/' ? module_dirlen : 0);
			clean_fname(dir, CFN_COLLAPSE_DOT_DOT_DIRS);
			if (check_filter(elp, FLOG, dir, 1) < 0)
				goto options_rejected;
		}
	}

	if (!backup_suffix)
		backup_suffix = backup_dir ? "" : BACKUP_SUFFIX;
	backup_suffix_len = strlen(backup_suffix);
	if (strchr(backup_suffix, '/') != NULL) {
		snprintf(err_buf, sizeof err_buf,
			"--suffix cannot contain slashes: %s\n",
			backup_suffix);
		goto cleanup;
	}
	if (backup_dir) {
		size_t len;
		make_backups = 1; /* --backup-dir implies --backup */
		while (*backup_dir == '.' && backup_dir[1] == '/')
			backup_dir += 2;
		if (*backup_dir == '.' && backup_dir[1] == '\0')
			backup_dir++;
		len = strlcpy(backup_dir_buf, backup_dir, sizeof backup_dir_buf);
		if (len > sizeof backup_dir_buf - 128) {
			snprintf(err_buf, sizeof err_buf,
				"the --backup-dir path is WAY too long.\n");
			goto cleanup;
		}
		backup_dir_len = (int)len;
		if (!backup_dir_len) {
			backup_dir_len = -1;
			backup_dir = NULL;
		} else if (backup_dir_buf[backup_dir_len - 1] != '/') {
			backup_dir_buf[backup_dir_len++] = '/';
			backup_dir_buf[backup_dir_len] = '\0';
		}
		backup_dir_remainder = sizeof backup_dir_buf - backup_dir_len;
	}
	if (backup_dir) {
		/* No need for a suffix or a protect rule. */
	} else if (!backup_suffix_len && (!am_server || !am_sender)) {
		snprintf(err_buf, sizeof err_buf,
			"--suffix cannot be empty %s\n", backup_dir_len < 0
			? "when --backup-dir is the same as the dest dir"
			: "without a --backup-dir");
		goto cleanup;
	} else if (make_backups && delete_mode && !delete_excluded && !am_server) {
		snprintf(backup_dir_buf, sizeof backup_dir_buf,
			"P *%s", backup_suffix);
		parse_filter_str(&filter_list, backup_dir_buf, rule_template(0), 0);
	}

	if (make_backups && !backup_dir)
		omit_dir_times = -1; /* Implied, so avoid -O to sender. */

	if (stdout_format) {
		if (am_server && log_format_has(stdout_format, 'I'))
			stdout_format_has_i = 2;
		else if (log_format_has(stdout_format, 'i'))
			stdout_format_has_i = itemize_changes | 1;
		if (!log_format_has(stdout_format, 'b')
		 && !log_format_has(stdout_format, 'c')
		 && !log_format_has(stdout_format, 'C'))
			log_before_transfer = !am_server;
	} else if (itemize_changes) {
		stdout_format = "%i %n%L";
		stdout_format_has_i = itemize_changes;
		log_before_transfer = !am_server;
	}

	if (do_progress && !am_server) {
		if (!log_before_transfer && INFO_EQ(NAME, 0))
			parse_output_words(info_words, info_levels, "name", DEFAULT_PRIORITY);
		parse_output_words(info_words, info_levels, "FLIST2,PROGRESS", DEFAULT_PRIORITY);
	}

	if (dry_run)
		do_xfers = 0;

	set_io_timeout(io_timeout);

	if (INFO_GTE(NAME, 1) && !stdout_format) {
		stdout_format = "%n%L";
		log_before_transfer = !am_server;
	}
	if (stdout_format_has_i || log_format_has(stdout_format, 'o'))
		stdout_format_has_o_or_i = 1;

	if (logfile_name && !am_daemon) {
		if (!logfile_format) {
			logfile_format = "%i %n%L";
			logfile_format_has_i = logfile_format_has_o_or_i = 1;
		} else {
			if (log_format_has(logfile_format, 'i'))
				logfile_format_has_i = 1;
			if (logfile_format_has_i || log_format_has(logfile_format, 'o'))
				logfile_format_has_o_or_i = 1;
		}
		log_init(0);
	} else if (!am_daemon)
		logfile_format = NULL;

	if (daemon_bwlimit && (!bwlimit || bwlimit > daemon_bwlimit))
		bwlimit = daemon_bwlimit;
	if (bwlimit) {
		bwlimit_writemax = (size_t)bwlimit * 128;
		if (bwlimit_writemax < 512)
			bwlimit_writemax = 512;
	}

	if (append_mode) {
		if (whole_file > 0) {
			snprintf(err_buf, sizeof err_buf,
				 "--append cannot be used with --whole-file\n");
			goto cleanup;
		}
		if (refused_inplace) {
			create_refuse_error(refused_inplace);
			goto cleanup;
		}
		inplace = 1;
	}

	if (write_devices) {
		if (refused_inplace) {
			create_refuse_error(refused_inplace);
			goto cleanup;
		}
		inplace = 1;
	}

	if (delay_updates && !partial_dir)
		partial_dir = tmp_partialdir;

	if (inplace) {
#ifdef HAVE_FTRUNCATE
		if (partial_dir) {
			snprintf(err_buf, sizeof err_buf,
				 "--%s cannot be used with --%s\n",
				 append_mode ? "append" : "inplace",
				 delay_updates ? "delay-updates" : "partial-dir");
			goto cleanup;
		}
		/* --inplace implies --partial for refusal purposes, but we
		 * clear the keep_partial flag for internal logic purposes. */
		if (refused_partial) {
			create_refuse_error(refused_partial);
			goto cleanup;
		}
		keep_partial = 0;
#else
		snprintf(err_buf, sizeof err_buf,
			 "--%s is not supported on this %s\n",
			 append_mode ? "append" : "inplace",
			 am_server ? "server" : "client");
		goto cleanup;
#endif
	} else {
		if (keep_partial && !partial_dir && !am_server) {
			if ((arg = getenv("RSYNC_PARTIAL_DIR")) != NULL && *arg)
				partial_dir = strdup(arg);
		}
		if (partial_dir) {
			if (*partial_dir)
				clean_fname(partial_dir, CFN_COLLAPSE_DOT_DOT_DIRS);
			if (!*partial_dir || strcmp(partial_dir, ".") == 0)
				partial_dir = NULL;
			if (!partial_dir && refused_partial) {
				create_refuse_error(refused_partial);
				goto cleanup;
			}
			keep_partial = 1;
		}
	}

	if (files_from) {
		char *h, *p;
		int q;
		if (argc > 2 || (!am_daemon && !am_server && argc == 1)) {
			usage(FERROR);
			exit_cleanup(RERR_SYNTAX);
		}
		if (strcmp(files_from, "-") == 0) {
			filesfrom_fd = 0;
			if (am_server)
				filesfrom_host = ""; /* reading from socket */
		} else if ((p = check_for_hostspec(files_from, &h, &q)) != 0) {
			if (am_server) {
				snprintf(err_buf, sizeof err_buf,
					"The --files-from sent to the server cannot specify a host.\n");
				goto cleanup;
			}
			files_from = p;
			filesfrom_host = h;
			if (strcmp(files_from, "-") == 0) {
				snprintf(err_buf, sizeof err_buf,
					"Invalid --files-from remote filename\n");
				goto cleanup;
			}
		} else {
			if (sanitize_paths)
				files_from = sanitize_path(NULL, files_from, NULL, 0, SP_DEFAULT);
			if (daemon_filter_list.head) {
				char *dir;
				if (!*files_from)
					goto options_rejected;
				dir = files_from + (*files_from == '/' ? module_dirlen : 0);
				clean_fname(dir, CFN_COLLAPSE_DOT_DOT_DIRS);
				if (check_filter(&daemon_filter_list, FLOG, dir, 0) < 0)
					goto options_rejected;
			}
			filesfrom_fd = open(files_from, O_RDONLY|O_BINARY);
			if (filesfrom_fd < 0) {
				snprintf(err_buf, sizeof err_buf,
					"failed to open files-from file %s: %s\n",
					files_from, strerror(errno));
				goto cleanup;
			}
		}
	}

	if (trust_sender || am_server || read_batch)
		trust_sender_args = trust_sender_filter = 1;
	else if (old_style_args || filesfrom_host != NULL)
		trust_sender_args = 1;

	am_starting_up = 0;

	return 1;

  options_rejected:
	snprintf(err_buf, sizeof err_buf,
		"Your options have been rejected by the server.\n");
  cleanup:
	if (pc)
		poptFreeContext(pc);
	return 0;
}


static char SPLIT_ARG_WHEN_OLD[1];

/**
 * Do backslash quoting of any weird chars in "arg", append the resulting
 * string to the end of the "opt" (which gets a "=" appended if it is not
 * an empty or NULL string), and return the (perhaps malloced) result.
 * If opt is NULL, arg is considered a filename arg that allows wildcards.
 * If it is "" or any other value, it is considered an option.
 **/
char *safe_arg(const char *opt, const char *arg)
{
#define SHELL_CHARS "!#$&;|<>(){}\"'` \t\\"
#define WILD_CHARS  "*?[]" /* We don't allow remote brace expansion */
	BOOL is_filename_arg = !opt;
	char *escapes = is_filename_arg ? SHELL_CHARS : WILD_CHARS SHELL_CHARS;
	BOOL escape_leading_dash = is_filename_arg && *arg == '-';
	BOOL escape_leading_tilde = 0;
	int len1 = opt && *opt ? strlen(opt) + 1 : 0;
	int len2 = strlen(arg);
	int extras = escape_leading_dash ? 2 : 0;
	char *ret;
	if (!protect_args && old_style_args < 2 && (!old_style_args || (!is_filename_arg && opt != SPLIT_ARG_WHEN_OLD))) {
		const char *f;
		if (*arg == '~' && is_filename_arg && !am_sender && !trust_sender_args
		 && ((relative_paths && !strstr(arg, "/./"))
		  || !strchr(arg, '/'))) {
			extras++;
			escape_leading_tilde = 1;
		}
		for (f = arg; *f; f++) {
			if (strchr(escapes, *f))
				extras++;
		}
	}
	if (!len1 && !extras)
		return (char*)arg;
	ret = new_array(char, len1 + len2 + extras + 1);
	if (len1) {
		memcpy(ret, opt, len1-1);
		ret[len1-1] = '=';
	}
	if (escape_leading_dash) {
		ret[len1++] = '.';
		ret[len1++] = '/';
		extras -= 2;
	}
	if (!extras)
		memcpy(ret + len1, arg, len2);
	else {
		const char *f = arg;
		char *t = ret + len1;
		if (escape_leading_tilde)
			*t++ = '\\';
		while (*f) {
			if (*f == '\\') {
				if (!is_filename_arg || !strchr(WILD_CHARS, f[1]))
					*t++ = '\\';
			} else if (strchr(escapes, *f))
				*t++ = '\\';
			*t++ = *f++;
		}
	}
	ret[len1+len2+extras] = '\0';
	return ret;
}


/**
 * Construct a filtered list of options to pass through from the
 * client to the server.
 *
 * This involves setting options that will tell the server how to
 * behave, and also filtering out options that are processed only
 * locally.
 **/
void server_options(char **args, int *argc_p)
{
	static char argstr[64];
	int ac = *argc_p;
	uchar where;
	char *arg;
	int i, x;

	/* This should always remain first on the server's command-line. */
	args[ac++] = "--server";

	if (!am_sender)
		args[ac++] = "--sender";

	x = 1;
	argstr[0] = '-';

	if (protect_args)
		argstr[x++] = 's';

	for (i = 0; i < verbose; i++)
		argstr[x++] = 'v';

	if (quiet && msgs2stderr)
		argstr[x++] = 'q';
	if (make_backups)
		argstr[x++] = 'b';
	if (update_only)
		argstr[x++] = 'u';
	if (!do_xfers) /* Note: NOT "dry_run"! */
		argstr[x++] = 'n';
	if (preserve_links)
		argstr[x++] = 'l';
	if ((xfer_dirs >= 2 && xfer_dirs < 4)
	 || (xfer_dirs && !recurse && (list_only || (delete_mode && am_sender))))
		argstr[x++] = 'd';
	if (am_sender) {
		if (keep_dirlinks)
			argstr[x++] = 'K';
		if (prune_empty_dirs)
			argstr[x++] = 'm';
		if (omit_dir_times > 0)
			argstr[x++] = 'O';
		if (omit_link_times)
			argstr[x++] = 'J';
		if (fuzzy_basis) {
			argstr[x++] = 'y';
			if (fuzzy_basis > 1)
				argstr[x++] = 'y';
		}
	} else {
		if (copy_links)
			argstr[x++] = 'L';
		if (copy_dirlinks)
			argstr[x++] = 'k';
	}

	if (whole_file > 0)
		argstr[x++] = 'W';
	/* We don't need to send --no-whole-file, because it's the
	 * default for remote transfers, and in any case old versions
	 * of rsync will not understand it. */

	if (preserve_hard_links) {
		argstr[x++] = 'H';
		if (preserve_hard_links > 1)
			argstr[x++] = 'H';
	}
	if (preserve_uid)
		argstr[x++] = 'o';
	if (preserve_gid)
		argstr[x++] = 'g';
	if (preserve_devices) /* ignore preserve_specials here */
		argstr[x++] = 'D';
	if (preserve_mtimes)
		argstr[x++] = 't';
	if (preserve_atimes) {
		argstr[x++] = 'U';
		if (preserve_atimes > 1)
			argstr[x++] = 'U';
	}
#ifdef SUPPORT_CRTIMES
	if (preserve_crtimes)
		argstr[x++] = 'N';
#endif
	if (preserve_perms)
		argstr[x++] = 'p';
	else if (preserve_executability && am_sender)
		argstr[x++] = 'E';
#ifdef SUPPORT_ACLS
	if (preserve_acls)
		argstr[x++] = 'A';
#endif
#ifdef SUPPORT_XATTRS
	if (preserve_xattrs) {
		argstr[x++] = 'X';
		if (preserve_xattrs > 1)
			argstr[x++] = 'X';
	}
#endif
	if (recurse)
		argstr[x++] = 'r';
	if (always_checksum)
		argstr[x++] = 'c';
	if (cvs_exclude)
		argstr[x++] = 'C';
	if (ignore_times)
		argstr[x++] = 'I';
	if (relative_paths)
		argstr[x++] = 'R';
	if (one_file_system) {
		argstr[x++] = 'x';
		if (one_file_system > 1)
			argstr[x++] = 'x';
	}
	if (sparse_files)
		argstr[x++] = 'S';
	if (do_compression == CPRES_ZLIB)
		argstr[x++] = 'z';

	set_allow_inc_recurse();

	/* This '\0'-terminates argstr and makes sure it didn't overflow. */
	x += maybe_add_e_option(argstr + x, (int)sizeof argstr - x);

	if (x > 1)
		args[ac++] = argstr;

#ifdef ICONV_OPTION
	if (iconv_opt) {
		char *set = strchr(iconv_opt, ',');
		if (set)
			set++;
		else
			set = iconv_opt;
		args[ac++] = safe_arg("--iconv", set);
	}
#endif

	if (protect_args && !local_server) /* unprotected args stop here */
		args[ac++] = NULL;

	if (list_only > 1)
		args[ac++] = "--list-only";

	/* This makes sure that the remote rsync can handle deleting with -d
	 * sans -r because the --no-r option was added at the same time. */
	if (xfer_dirs && !recurse && delete_mode && am_sender)
		args[ac++] = "--no-r";

	if (do_compression && do_compression_level != CLVL_NOT_SPECIFIED) {
		if (asprintf(&arg, "--compress-level=%d", do_compression_level) < 0)
			goto oom;
		args[ac++] = arg;
	}

	if (preserve_devices) {
		/* Note: sending "--devices" would not be backward-compatible. */
		if (!preserve_specials)
			args[ac++] = "--no-specials"; /* -D is already set. */
	} else if (preserve_specials)
		args[ac++] = "--specials";

	/* The server side doesn't use our log-format, but in certain
	 * circumstances they need to know a little about the option. */
	if (stdout_format && am_sender) {
		/* Use --log-format, not --out-format, for compatibility. */
		if (stdout_format_has_i > 1)
			args[ac++] = "--log-format=%i%I";
		else if (stdout_format_has_i)
			args[ac++] = "--log-format=%i";
		else if (stdout_format_has_o_or_i)
			args[ac++] = "--log-format=%o";
		else if (!verbose)
			args[ac++] = "--log-format=X";
	}

	if (msgs2stderr == 1)
		args[ac++] = "--msgs2stderr";
	else if (msgs2stderr == 0)
		args[ac++] = "--no-msgs2stderr";

	if (block_size) {
		if (asprintf(&arg, "-B%u", (int)block_size) < 0)
			goto oom;
		args[ac++] = arg;
	}

	if (io_timeout) {
		if (asprintf(&arg, "--timeout=%d", io_timeout) < 0)
			goto oom;
		args[ac++] = arg;
	}

	if (bwlimit) {
		if (asprintf(&arg, "--bwlimit=%d", bwlimit) < 0)
			goto oom;
		args[ac++] = arg;
	}

	if (backup_dir) {
		/* This split idiom allows for ~/path expansion via the shell. */
		args[ac++] = "--backup-dir";
		args[ac++] = safe_arg("", backup_dir);
	}

	/* Only send --suffix if it specifies a non-default value. */
	if (strcmp(backup_suffix, backup_dir ? "" : BACKUP_SUFFIX) != 0)
		args[ac++] = safe_arg("--suffix", backup_suffix);

	if (checksum_choice)
		args[ac++] = safe_arg("--checksum-choice", checksum_choice);

	if (do_compression == CPRES_ZLIBX)
		args[ac++] = "--new-compress";
	else if (compress_choice && do_compression == CPRES_ZLIB)
		args[ac++] = "--old-compress";
	else if (compress_choice)
		args[ac++] = safe_arg("--compress-choice", compress_choice);

	if (am_sender) {
		if (max_delete > 0) {
			if (asprintf(&arg, "--max-delete=%d", max_delete) < 0)
				goto oom;
			args[ac++] = arg;
		} else if (max_delete == 0)
			args[ac++] = "--max-delete=-1";
		if (min_size >= 0)
			args[ac++] = safe_arg("--min-size", min_size_arg);
		if (max_size >= 0)
			args[ac++] = safe_arg("--max-size", max_size_arg);
		if (delete_before)
			args[ac++] = "--delete-before";
		else if (delete_during == 2)
			args[ac++] = "--delete-delay";
		else if (delete_during)
			args[ac++] = "--delete-during";
		else if (delete_after)
			args[ac++] = "--delete-after";
		else if (delete_mode && !delete_excluded)
			args[ac++] = "--delete";
		if (delete_excluded)
			args[ac++] = "--delete-excluded";
		if (force_delete)
			args[ac++] = "--force";
		if (write_batch < 0)
			args[ac++] = "--only-write-batch=X";
		if (am_root > 1)
			args[ac++] = "--super";
		if (size_only)
			args[ac++] = "--size-only";
		if (do_stats)
			args[ac++] = "--stats";
	} else {
		if (skip_compress)
			args[ac++] = safe_arg("--skip-compress", skip_compress);
	}

	if (max_alloc_arg && max_alloc != DEFAULT_MAX_ALLOC)
		args[ac++] = safe_arg("--max-alloc", max_alloc_arg);

	/* --delete-missing-args needs the cooperation of both sides, but
	 * the sender can handle --ignore-missing-args by itself. */
	if (missing_args == 2)
		args[ac++] = "--delete-missing-args";
	else if (missing_args == 1 && !am_sender)
		args[ac++] = "--ignore-missing-args";

	if (modify_window_set && am_sender) {
		char *fmt = modify_window < 0 ? "-@%d" : "--modify-window=%d";
		if (asprintf(&arg, fmt, modify_window) < 0)
			goto oom;
		args[ac++] = arg;
	}

	if (checksum_seed) {
		if (asprintf(&arg, "--checksum-seed=%d", checksum_seed) < 0)
			goto oom;
		args[ac++] = arg;
	}

	if (partial_dir && am_sender) {
		if (partial_dir != tmp_partialdir) {
			args[ac++] = "--partial-dir";
			args[ac++] = safe_arg("", partial_dir);
		}
		if (delay_updates)
			args[ac++] = "--delay-updates";
	} else if (keep_partial && am_sender)
		args[ac++] = "--partial";

	if (ignore_errors)
		args[ac++] = "--ignore-errors";

	if (copy_unsafe_links)
		args[ac++] = "--copy-unsafe-links";

	if (safe_symlinks)
		args[ac++] = "--safe-links";

	if (numeric_ids)
		args[ac++] = "--numeric-ids";

	if (use_qsort)
		args[ac++] = "--use-qsort";

	if (am_sender) {
		if (usermap)
			args[ac++] = safe_arg("--usermap", usermap);

		if (groupmap)
			args[ac++] = safe_arg("--groupmap", groupmap);

		if (ignore_existing)
			args[ac++] = "--ignore-existing";

		/* Backward compatibility: send --existing, not --ignore-non-existing. */
		if (ignore_non_existing)
			args[ac++] = "--existing";

		if (tmpdir) {
			args[ac++] = "--temp-dir";
			args[ac++] = safe_arg("", tmpdir);
		}

		if (do_fsync)
			args[ac++] = "--fsync";

		if (basis_dir[0]) {
			/* the server only needs this option if it is not the sender,
			 *   and it may be an older version that doesn't know this
			 *   option, so don't send it if client is the sender.
			 */
			for (i = 0; i < basis_dir_cnt; i++) {
				args[ac++] = alt_dest_opt(0);
				args[ac++] = safe_arg("", basis_dir[i]);
			}
		}
	}

	/* What flags do we need to send to the other side? */
	where = (am_server ? W_CLI : W_SRV) | (am_sender ? W_REC : W_SND);
	arg = make_output_option(info_words, info_levels, where);
	if (arg)
		args[ac++] = arg;

	if (append_mode) {
		if (append_mode > 1)
			args[ac++] = "--append";
		args[ac++] = "--append";
	} else if (inplace) {
		args[ac++] = "--inplace";
		/* Work around a bug in older rsync versions (on the remote side) for --inplace --sparse */
		if (sparse_files && !whole_file && am_sender)
			args[ac++] = "--no-W";
	}

	if (files_from && (!am_sender || filesfrom_host)) {
		if (filesfrom_host) {
			args[ac++] = "--files-from";
			args[ac++] = safe_arg("", files_from);
			if (eol_nulls)
				args[ac++] = "--from0";
		} else {
			args[ac++] = "--files-from=-";
			args[ac++] = "--from0";
		}
		if (!relative_paths)
			args[ac++] = "--no-relative";
	}
	/* It's OK that this checks the upper-bound of the protocol_version. */
	if (relative_paths && !implied_dirs && (!am_sender || protocol_version >= 30))
		args[ac++] = "--no-implied-dirs";

	if (write_devices && am_sender)
		args[ac++] = "--write-devices";

	if (remove_source_files == 1)
		args[ac++] = "--remove-source-files";
	else if (remove_source_files)
		args[ac++] = "--remove-sent-files";

	if (copy_devices && !am_sender)
		args[ac++] = "--copy-devices";

	if (preallocate_files && am_sender)
		args[ac++] = "--preallocate";

	if (open_noatime && preserve_atimes <= 1)
		args[ac++] = "--open-noatime";

	if (mkpath_dest_arg && am_sender)
		args[ac++] = "--mkpath";

	if (ac > MAX_SERVER_ARGS) { /* Not possible... */
		rprintf(FERROR, "argc overflow in server_options().\n");
		exit_cleanup(RERR_MALLOC);
	}

	if (remote_option_cnt) {
		int j;
		if (ac + remote_option_cnt > MAX_SERVER_ARGS) {
			rprintf(FERROR, "too many remote options specified.\n");
			exit_cleanup(RERR_SYNTAX);
		}
		for (j = 1; j <= remote_option_cnt; j++)
			args[ac++] = safe_arg(SPLIT_ARG_WHEN_OLD, remote_options[j]);
	}

	*argc_p = ac;
	return;

    oom:
	out_of_memory("server_options");
}

int maybe_add_e_option(char *buf, int buf_len)
{
	int x = 0;

	/* We don't really know the actual protocol_version at this point,
	 * but checking the pre-negotiated value allows the user to use a
	 * --protocol=29 override to avoid the use of this -eFLAGS opt. */
	if (protocol_version >= 30 && buf_len > 0) {
		/* We make use of the -e option to let the server know about
		 * any pre-release protocol version && some behavior flags. */
		buf[x++] = 'e';

#if SUBPROTOCOL_VERSION != 0
		if (protocol_version == PROTOCOL_VERSION)
			x += snprintf(buf + x, buf_len - x, "%d.%d", PROTOCOL_VERSION, SUBPROTOCOL_VERSION);
		else
#endif
			buf[x++] = '.';
		if (allow_inc_recurse)
			buf[x++] = 'i';
#ifdef CAN_SET_SYMLINK_TIMES
		buf[x++] = 'L'; /* symlink time-setting support */
#endif
#ifdef ICONV_OPTION
		buf[x++] = 's'; /* symlink iconv translation support */
#endif
		buf[x++] = 'f'; /* flist I/O-error safety support */
		buf[x++] = 'x'; /* xattr hardlink optimization not desired */
		buf[x++] = 'C'; /* support checksum seed order fix */
		buf[x++] = 'I'; /* support inplace_partial behavior */
		buf[x++] = 'v'; /* use varint for flist & compat flags; negotiate checksum */
		buf[x++] = 'u'; /* include name of uid 0 & gid 0 in the id map */

		/* NOTE: Avoid using 'V' -- it was represented with the high bit of a write_byte() that became a write_varint(). */
	}

	if (x >= buf_len) { /* Not possible... */
		rprintf(FERROR, "overflow in add_e_flags().\n");
		exit_cleanup(RERR_MALLOC);
	}

	buf[x] = '\0';

	return x;
}

/* If str points to a valid hostspec, return allocated memory containing the
 * [USER@]HOST part of the string, and set the path_start_ptr to the part of
 * the string after the host part.  Otherwise, return NULL.  If port_ptr is
 * non-NULL, we must be parsing an rsync:// URL hostname, and we will set
 * *port_ptr if a port number is found.  Note that IPv6 IPs will have their
 * (required for parsing) [ and ] chars elided from the returned string. */
static char *parse_hostspec(char *str, char **path_start_ptr, int *port_ptr)
{
	char *s, *host_start = str;
	int hostlen = 0, userlen = 0;
	char *ret;

	for (s = str; ; s++) {
		if (!*s) {
			/* It is only OK if we run out of string with rsync:// */
			if (!port_ptr)
				return NULL;
			if (!hostlen)
				hostlen = s - host_start;
			break;
		}
		if (*s == ':' || *s == '/') {
			if (!hostlen)
				hostlen = s - host_start;
			if (*s++ == '/') {
				if (!port_ptr)
					return NULL;
			} else if (port_ptr) {
				*port_ptr = atoi(s);
				while (isDigit(s)) s++;
				if (*s && *s++ != '/')
					return NULL;
			}
			break;
		}
		if (*s == '@') {
			userlen = s - str + 1;
			host_start = s + 1;
		} else if (*s == '[') {
			if (s != host_start++)
				return NULL;
			while (*s && *s != ']' && *s != '/') s++; /*SHARED ITERATOR*/
			hostlen = s - host_start;
			if (*s != ']' || (s[1] && s[1] != '/' && s[1] != ':') || !hostlen)
				return NULL;
		}
	}

	*path_start_ptr = s;
	ret = new_array(char, userlen + hostlen + 1);
	if (userlen)
		strlcpy(ret, str, userlen + 1);
	strlcpy(ret + userlen, host_start, hostlen + 1);
	return ret;
}

/* Look for a HOST specification of the form "HOST:PATH", "HOST::PATH", or
 * "rsync://HOST:PORT/PATH".  If found, *host_ptr will be set to some allocated
 * memory with the HOST.  If a daemon-accessing spec was specified, the value
 * of *port_ptr will contain a non-0 port number, otherwise it will be set to
 * 0.  The return value is a pointer to the PATH.  Note that the HOST spec can
 * be an IPv6 literal address enclosed in '[' and ']' (such as "[::1]" or
 * "[::ffff:127.0.0.1]") which is returned without the '[' and ']'. */
char *check_for_hostspec(char *s, char **host_ptr, int *port_ptr)
{
	char *path;

	if (port_ptr && strncasecmp(URL_PREFIX, s, strlen(URL_PREFIX)) == 0) {
		*host_ptr = parse_hostspec(s + strlen(URL_PREFIX), &path, port_ptr);
		if (*host_ptr) {
			if (!*port_ptr)
				*port_ptr = -1; /* -1 indicates they want the default */
			return path;
		}
	}

	*host_ptr = parse_hostspec(s, &path, NULL);
	if (!*host_ptr)
		return NULL;

	if (*path == ':') {
		if (port_ptr && !*port_ptr)
			*port_ptr = -1;
		return path + 1;
	}
	if (port_ptr)
		*port_ptr = 0;

	return path;
}
/* This modules is based on the params.c module from Samba, written by Karl Auer
   and much modified by Christopher Hertel. */

/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

/* -------------------------------------------------------------------------- **
 *
 * Module name: params
 *
 * -------------------------------------------------------------------------- **
 *
 *  This module performs lexical analysis and initial parsing of a
 *  Windows-like parameter file.  It recognizes and handles four token
 *  types:  section-name, parameter-name, parameter-value, and
 *  end-of-file.  Comments and line continuation are handled
 *  internally.
 *
 *  The entry point to the module is function pm_process().  This
 *  function opens the source file, calls the Parse() function to parse
 *  the input, and then closes the file when either the EOF is reached
 *  or a fatal error is encountered.
 *
 *  A sample parameter file might look like this:
 *
 *  [section one]
 *  parameter one = value string
 *  parameter two = another value
 *  [section two]
 *  new parameter = some value or t'other
 *
 *  The parameter file is divided into sections by section headers:
 *  section names enclosed in square brackets (eg. [section one]).
 *  Each section contains parameter lines, each of which consist of a
 *  parameter name and value delimited by an equal sign.  Roughly, the
 *  syntax is:
 *
 *    <file>            :==  { <section> } EOF
 *
 *    <section>         :==  <section header> { <parameter line> }
 *
 *    <section header>  :==  '[' NAME ']'
 *
 *    <parameter line>  :==  NAME '=' VALUE '\n'
 *
 *  Blank lines and comment lines are ignored.  Comment lines are lines
 *  beginning with either a semicolon (';') or a pound sign ('#').
 *
 *  All whitespace in section names and parameter names is compressed
 *  to single spaces.  Leading and trailing whitespace is stripped from
 *  both names and values.
 *
 *  Only the first equals sign in a parameter line is significant.
 *  Parameter values may contain equals signs, square brackets and
 *  semicolons.  Internal whitespace is retained in parameter values,
 *  with the exception of the '\r' character, which is stripped for
 *  historic reasons.  Parameter names may not start with a left square
 *  bracket, an equal sign, a pound sign, or a semicolon, because these
 *  are used to identify other tokens.
 *
 * -------------------------------------------------------------------------- **
 */

#include "rsync.h"
#include "ifuncs.h"
#include "itypes.h"

/* -------------------------------------------------------------------------- **
 * Constants...
 */

#define BUFR_INC 1024


/* -------------------------------------------------------------------------- **
 * Variables...
 *
 *  bufr        - pointer to a global buffer.  This is probably a kludge,
 *                but it was the nicest kludge I could think of (for now).
 *  bSize       - The size of the global buffer <bufr>.
 */

static char *bufr  = NULL;
static int   bSize = 0;
static BOOL  (*the_sfunc)(char *);
static BOOL  (*the_pfunc)(char *, char *);

/* -------------------------------------------------------------------------- **
 * Functions...
 */

static int EatWhitespace( FILE *InFile )
  /* ------------------------------------------------------------------------ **
   * Scan past whitespace (see ctype(3C)) and return the first non-whitespace
   * character, or newline, or EOF.
   *
   *  Input:  InFile  - Input source.
   *
   *  Output: The next non-whitespace character in the input stream.
   *
   *  Notes:  Because the config files use a line-oriented grammar, we
   *          explicitly exclude the newline character from the list of
   *          whitespace characters.
   *        - Note that both EOF (-1) and the nul character ('\0') are
   *          considered end-of-file markers.
   *
   * ------------------------------------------------------------------------ **
   */
  {
  int c;

  for( c = getc( InFile ); isspace( c ) && ('\n' != c); c = getc( InFile ) )
    ;
  return( c );
  } /* EatWhitespace */

static int EatComment( FILE *InFile )
  /* ------------------------------------------------------------------------ **
   * Scan to the end of a comment.
   *
   *  Input:  InFile  - Input source.
   *
   *  Output: The character that marks the end of the comment.  Normally,
   *          this will be a newline, but it *might* be an EOF.
   *
   *  Notes:  Because the config files use a line-oriented grammar, we
   *          explicitly exclude the newline character from the list of
   *          whitespace characters.
   *        - Note that both EOF (-1) and the nul character ('\0') are
   *          considered end-of-file markers.
   *
   * ------------------------------------------------------------------------ **
   */
  {
  int c;

  for( c = getc( InFile ); ('\n'!=c) && (EOF!=c) && (c>0); c = getc( InFile ) )
    ;
  return( c );
  } /* EatComment */

static int Continuation( char *line, int pos )
  /* ------------------------------------------------------------------------ **
   * Scan backwards within a string to discover if the last non-whitespace
   * character is a line-continuation character ('\\').
   *
   *  Input:  line  - A pointer to a buffer containing the string to be
   *                  scanned.
   *          pos   - This is taken to be the offset of the end of the
   *                  string.  This position is *not* scanned.
   *
   *  Output: The offset of the '\\' character if it was found, or -1 to
   *          indicate that it was not.
   *
   * ------------------------------------------------------------------------ **
   */
  {
  pos--;
  while( pos >= 0 && isSpace(line + pos) )
     pos--;

  return( ((pos >= 0) && ('\\' == line[pos])) ? pos : -1 );
  } /* Continuation */


static BOOL Section( FILE *InFile, BOOL (*sfunc)(char *) )
  /* ------------------------------------------------------------------------ **
   * Scan a section name, and pass the name to function sfunc().
   *
   *  Input:  InFile  - Input source.
   *          sfunc   - Pointer to the function to be called if the section
   *                    name is successfully read.
   *
   *  Output: True if the section name was read and True was returned from
   *          <sfunc>.  False if <sfunc> failed or if a lexical error was
   *          encountered.
   *
   * ------------------------------------------------------------------------ **
   */
  {
  int   c;
  int   i;
  int   end;
  char *func  = "params.c:Section() -";

  i = 0;      /* <i> is the offset of the next free byte in bufr[] and  */
  end = 0;    /* <end> is the current "end of string" offset.  In most  */
              /* cases these will be the same, but if the last          */
              /* character written to bufr[] is a space, then <end>     */
              /* will be one less than <i>.                             */

  c = EatWhitespace( InFile );    /* We've already got the '['.  Scan */
                                  /* past initial white space.        */

  while( (EOF != c) && (c > 0) )
    {

    /* Check that the buffer is big enough for the next character. */
    if( i > (bSize - 2) )
      {
      bSize += BUFR_INC;
      bufr   = realloc_array( bufr, char, bSize );
      }

    /* Handle a single character. */
    switch( c )
      {
      case ']':                       /* Found the closing bracket.         */
        bufr[end] = '\0';
        if( 0 == end )                  /* Don't allow an empty name.       */
          {
          rprintf(FLOG, "%s Empty section name in config file.\n", func );
          return( False );
          }
        if( !sfunc( bufr ) )            /* Got a valid name.  Deal with it. */
          return( False );
        (void)EatComment( InFile );     /* Finish off the line.             */
        return( True );

      case '\n':                      /* Got newline before closing ']'.    */
        i = Continuation( bufr, i );    /* Check for line continuation.     */
        if( i < 0 )
          {
          bufr[end] = '\0';
          rprintf(FLOG, "%s Badly formed line in config file: %s\n",
                   func, bufr );
          return( False );
          }
        end = ( (i > 0) && (' ' == bufr[i - 1]) ) ? (i - 1) : (i);
        c = getc( InFile );             /* Continue with next line.         */
        break;

      default:                        /* All else are a valid name chars.   */
        if( isspace( c ) )              /* One space per whitespace region. */
          {
          bufr[end] = ' ';
          i = end + 1;
          c = EatWhitespace( InFile );
          }
        else                            /* All others copy verbatim.        */
          {
          bufr[i++] = c;
          end = i;
          c = getc( InFile );
          }
      }
    }

  /* We arrive here if we've met the EOF before the closing bracket. */
  rprintf(FLOG, "%s Unexpected EOF in the config file: %s\n", func, bufr );
  return( False );
  } /* Section */

static BOOL Parameter( FILE *InFile, BOOL (*pfunc)(char *, char *), int c )
  /* ------------------------------------------------------------------------ **
   * Scan a parameter name and value, and pass these two fields to pfunc().
   *
   *  Input:  InFile  - The input source.
   *          pfunc   - A pointer to the function that will be called to
   *                    process the parameter, once it has been scanned.
   *          c       - The first character of the parameter name, which
   *                    would have been read by Parse().  Unlike a comment
   *                    line or a section header, there is no lead-in
   *                    character that can be discarded.
   *
   *  Output: True if the parameter name and value were scanned and processed
   *          successfully, else False.
   *
   *  Notes:  This function is in two parts.  The first loop scans the
   *          parameter name.  Internal whitespace is compressed, and an
   *          equal sign (=) terminates the token.  Leading and trailing
   *          whitespace is discarded.  The second loop scans the parameter
   *          value.  When both have been successfully identified, they are
   *          passed to pfunc() for processing.
   *
   * ------------------------------------------------------------------------ **
   */
  {
  int   i       = 0;    /* Position within bufr. */
  int   end     = 0;    /* bufr[end] is current end-of-string. */
  int   vstart  = 0;    /* Starting position of the parameter value. */
  char *func    = "params.c:Parameter() -";

  /* Read the parameter name. */
  while( 0 == vstart )  /* Loop until we've found the start of the value. */
    {

    if( i > (bSize - 2) )       /* Ensure there's space for next char.    */
      {
      bSize += BUFR_INC;
      bufr   = realloc_array( bufr, char, bSize );
      }

    switch( c )
      {
      case '=':                 /* Equal sign marks end of param name. */
        if( 0 == end )              /* Don't allow an empty name.      */
          {
          rprintf(FLOG, "%s Invalid parameter name in config file.\n", func );
          return( False );
          }
        bufr[end++] = '\0';         /* Mark end of string & advance.   */
        i = vstart = end;           /* New string starts here.         */
        c = EatWhitespace(InFile);
        break;

      case '\n':                /* Find continuation char, else error. */
        i = Continuation( bufr, i );
        if( i < 0 )
          {
          bufr[end] = '\0';
          rprintf(FLOG, "%s Ignoring badly formed line in config file: %s\n",
                   func, bufr );
          return( True );
          }
        end = ( (i > 0) && (' ' == bufr[i - 1]) ) ? (i - 1) : (i);
        c = getc( InFile );       /* Read past eoln.                   */
        break;

      case '\0':                /* Shouldn't have EOF within param name. */
      case EOF:
        bufr[i] = '\0';
        rprintf(FLOG, "%s Unexpected end-of-file at: %s\n", func, bufr );
        return( True );

      case ' ':
      case '\t':
        /* A directive divides at the first space or tab. */
        if (*bufr == '&') {
          bufr[end++] = '\0';
          i = vstart = end;
          c = EatWhitespace(InFile);
          if (c == '=')
            c = EatWhitespace(InFile);
          break;
        }
        /* FALL THROUGH */

      default:
        if( isspace( c ) )     /* One ' ' per whitespace region.       */
          {
          bufr[end] = ' ';
          i = end + 1;
          c = EatWhitespace( InFile );
          }
        else                   /* All others verbatim.                 */
          {
          bufr[i++] = c;
          end = i;
          c = getc( InFile );
          }
      }
    }

  /* Now parse the value. */
  while( (EOF !=c) && (c > 0) )
    {

    if( i > (bSize - 2) )       /* Make sure there's enough room. */
      {
      bSize += BUFR_INC;
      bufr   = realloc_array( bufr, char, bSize );
      }

    switch( c )
      {
      case '\r':              /* Explicitly remove '\r' because the older */
        c = getc( InFile );   /* version called fgets_slash() which also  */
        break;                /* removes them.                            */

      case '\n':              /* Marks end of value unless there's a '\'. */
        i = Continuation( bufr, i );
        if( i < 0 )
          c = 0;
        else
          {
          for( end = i; end >= 0 && isSpace(bufr + end); end-- )
            ;
          c = getc( InFile );
          }
        break;

      default:               /* All others verbatim.  Note that spaces do */
        bufr[i++] = c;       /* not advance <end>.  This allows trimming  */
        if( !isspace( c ) )  /* of whitespace at the end of the line.     */
          end = i;
        c = getc( InFile );
        break;
      }
    }
  bufr[end] = '\0';          /* End of value. */

  return( pfunc( bufr, &bufr[vstart] ) );   /* Pass name & value to pfunc().  */
  } /* Parameter */

static int name_cmp(const void *n1, const void *n2)
{
    return strcmp(*(char * const *)n1, *(char * const *)n2);
}

static int include_config(char *include, int manage_globals)
{
    STRUCT_STAT sb;
    char *match = manage_globals ? "*.conf" : "*.inc";
    int ret;

    if (do_stat(include, &sb) < 0) {
	rsyserr(FLOG, errno, "unable to stat config file \"%s\"", include);
	return 0;
    }

    if (S_ISREG(sb.st_mode)) {
	if (manage_globals && the_sfunc)
	    the_sfunc("]push");
	ret = pm_process(include, the_sfunc, the_pfunc);
	if (manage_globals && the_sfunc)
	    the_sfunc("]pop");
    } else if (S_ISDIR(sb.st_mode)) {
	char buf[MAXPATHLEN], **bpp;
	item_list conf_list;
	struct dirent *di;
	size_t j;
	DIR *d;

	if (!(d = opendir(include))) {
	    rsyserr(FLOG, errno, "unable to open config dir \"%s\"", include);
	    return 0;
	}

	memset(&conf_list, 0, sizeof conf_list);

	while ((di = readdir(d)) != NULL) {
	    char *dname = d_name(di);
	    if (!wildmatch(match, dname))
		continue;
	    bpp = EXPAND_ITEM_LIST(&conf_list, char *, 32);
	    pathjoin(buf, sizeof buf, include, dname);
	    *bpp = strdup(buf);
	}
	closedir(d);

	if (!(bpp = conf_list.items))
	    return 1;

	if (conf_list.count > 1)
	    qsort(bpp, conf_list.count, sizeof (char *), name_cmp);

	for (j = 0, ret = 1; j < conf_list.count; j++) {
	    if (manage_globals && the_sfunc)
		the_sfunc(j == 0 ? "]push" : "]reset");
	    if ((ret = pm_process(bpp[j], the_sfunc, the_pfunc)) != 1)
		break;
	}

	if (manage_globals && the_sfunc)
	    the_sfunc("]pop");

	for (j = 0; j < conf_list.count; j++)
	    free(bpp[j]);
	free(bpp);
    } else
	ret = 0;

    return ret;
}

static int parse_directives(char *name, char *val)
{
    if (strcasecmp(name, "&include") == 0)
        return include_config(val, 1);
    if (strcasecmp(name, "&merge") == 0)
        return include_config(val, 0);
    rprintf(FLOG, "Unknown directive: %s.\n", name);
    return 0;
}

static int Parse( FILE *InFile,
                   BOOL (*sfunc)(char *),
                   BOOL (*pfunc)(char *, char *) )
  /* ------------------------------------------------------------------------ **
   * Scan & parse the input.
   *
   *  Input:  InFile  - Input source.
   *          sfunc   - Function to be called when a section name is scanned.
   *                    See Section().
   *          pfunc   - Function to be called when a parameter is scanned.
   *                    See Parameter().
   *
   *  Output: 1 if the file was successfully scanned, 2 if the file was
   *  scanned until a section header with no section function, else 0.
   *
   *  Notes:  The input can be viewed in terms of 'lines'.  There are four
   *          types of lines:
   *            Blank      - May contain whitespace, otherwise empty.
   *            Comment    - First non-whitespace character is a ';' or '#'.
   *                         The remainder of the line is ignored.
   *            Section    - First non-whitespace character is a '['.
   *            Parameter  - The default case.
   *
   * ------------------------------------------------------------------------ **
   */
  {
  int    c;

  c = EatWhitespace( InFile );
  while( (EOF != c) && (c > 0) )
    {
    switch( c )
      {
      case '\n':                        /* Blank line. */
        c = EatWhitespace( InFile );
        break;

      case ';':                         /* Comment line. */
      case '#':
        c = EatComment( InFile );
        break;

      case '[':                         /* Section Header. */
        if (!sfunc)
          return 2;
        if( !Section( InFile, sfunc ) )
          return 0;
        c = EatWhitespace( InFile );
        break;

      case '\\':                        /* Bogus backslash. */
        c = EatWhitespace( InFile );
        break;

      case '&':                         /* Handle directives */
        the_sfunc = sfunc;
        the_pfunc = pfunc;
        c = Parameter( InFile, parse_directives, c );
        if (c != 1)
          return c;
        c = EatWhitespace( InFile );
        break;

      default:                          /* Parameter line. */
        if( !Parameter( InFile, pfunc, c ) )
          return 0;
        c = EatWhitespace( InFile );
        break;
      }
    }
  return 1;
  } /* Parse */

static FILE *OpenConfFile( char *FileName )
  /* ------------------------------------------------------------------------ **
   * Open a config file.
   *
   *  Input:  FileName  - The pathname of the config file to be opened.
   *
   *  Output: A pointer of type (FILE *) to the opened file, or NULL if the
   *          file could not be opened.
   *
   * ------------------------------------------------------------------------ **
   */
  {
  FILE *OpenedFile;
  char *func = "params.c:OpenConfFile() -";

  if( NULL == FileName || 0 == *FileName )
    {
    rprintf(FLOG, "%s No config filename specified.\n", func);
    return( NULL );
    }

  OpenedFile = fopen( FileName, "r" );
  if( NULL == OpenedFile )
    {
    rsyserr(FLOG, errno, "unable to open config file \"%s\"",
	    FileName);
    }

  return( OpenedFile );
  } /* OpenConfFile */

int pm_process( char *FileName,
                 BOOL (*sfunc)(char *),
                 BOOL (*pfunc)(char *, char *) )
  /* ------------------------------------------------------------------------ **
   * Process the named parameter file.
   *
   *  Input:  FileName  - The pathname of the parameter file to be opened.
   *          sfunc     - A pointer to a function that will be called when
   *                      a section name is discovered.
   *          pfunc     - A pointer to a function that will be called when
   *                      a parameter name and value are discovered.
   *
   *  Output: 1 if the file was successfully parsed, 2 if parsing ended at a
   *  section header w/o a section function, else 0.
   *
   * ------------------------------------------------------------------------ **
   */
  {
  int   result;
  FILE *InFile;
  char *func = "params.c:pm_process() -";

  InFile = OpenConfFile( FileName );          /* Open the config file. */
  if( NULL == InFile )
    return( False );

  if( NULL != bufr )                          /* If we already have a buffer */
    result = Parse( InFile, sfunc, pfunc );   /* (recursive call), then just */
                                              /* use it.                     */

  else                                        /* If we don't have a buffer   */
    {                                         /* allocate one, then parse,   */
    bSize = BUFR_INC;                         /* then free.                  */
    bufr = new_array( char, bSize );
    result = Parse( InFile, sfunc, pfunc );
    free( bufr );
    bufr  = NULL;
    bSize = 0;
    }

  fclose(InFile);

  if( !result )                               /* Generic failure. */
    {
    rprintf(FLOG, "%s Failed.  Error returned from params.c:parse().\n", func);
    return 0;
    }

  return result;
  } /* pm_process */

/* -------------------------------------------------------------------------- */

/*
 * Routines used to setup various kinds of inter-process pipes.
 *
 * Copyright (C) 1996-2000 Andrew Tridgell
 * Copyright (C) 1996 Paul Mackerras
 * Copyright (C) 2001, 2002 Martin Pool <mbp@samba.org>
 * Copyright (C) 2004-2020 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"

extern int am_sender;
extern int am_server;
extern int blocking_io;
extern int filesfrom_fd;
extern int munge_symlinks;
extern char *logfile_name;
extern int remote_option_cnt;
extern const char **remote_options;
extern struct chmod_mode_struct *chmod_modes;

/**
 * Create a child connected to us via its stdin/stdout.
 *
 * This is derived from CVS code
 *
 * Note that in the child STDIN is set to blocking and STDOUT
 * is set to non-blocking. This is necessary as rsh relies on stdin being blocking
 *  and ssh relies on stdout being non-blocking
 *
 * If blocking_io is set then use blocking io on both fds. That can be
 * used to cope with badly broken rsh implementations like the one on
 * Solaris.
 **/
pid_t piped_child(char **command, int *f_in, int *f_out)
{
	pid_t pid;
	int to_child_pipe[2];
	int from_child_pipe[2];

	if (DEBUG_GTE(CMD, 1))
		print_child_argv("opening connection using:", command);

	if (fd_pair(to_child_pipe) < 0 || fd_pair(from_child_pipe) < 0) {
		rsyserr(FERROR, errno, "pipe");
		exit_cleanup(RERR_IPC);
	}

	pid = do_fork();
	if (pid == -1) {
		rsyserr(FERROR, errno, "fork");
		exit_cleanup(RERR_IPC);
	}

	if (pid == 0) {
		if (dup2(to_child_pipe[0], STDIN_FILENO) < 0
		 || close(to_child_pipe[1]) < 0
		 || close(from_child_pipe[0]) < 0
		 || dup2(from_child_pipe[1], STDOUT_FILENO) < 0) {
			rsyserr(FERROR, errno, "Failed to dup/close");
			exit_cleanup(RERR_IPC);
		}
		if (to_child_pipe[0] != STDIN_FILENO)
			close(to_child_pipe[0]);
		if (from_child_pipe[1] != STDOUT_FILENO)
			close(from_child_pipe[1]);
		set_blocking(STDIN_FILENO);
		if (blocking_io > 0)
			set_blocking(STDOUT_FILENO);
		execvp(command[0], command);
		rsyserr(FERROR, errno, "Failed to exec %s", command[0]);
		exit_cleanup(RERR_IPC);
	}

	if (close(from_child_pipe[1]) < 0 || close(to_child_pipe[0]) < 0) {
		rsyserr(FERROR, errno, "Failed to close");
		exit_cleanup(RERR_IPC);
	}

	*f_in = from_child_pipe[0];
	*f_out = to_child_pipe[1];

	return pid;
}

/* This function forks a child which calls child_main().  First,
 * however, it has to establish communication paths to and from the
 * newborn child.  It creates two socket pairs -- one for writing to
 * the child (from the parent) and one for reading from the child
 * (writing to the parent).  Since that's four socket ends, each
 * process has to close the two ends it doesn't need.  The remaining
 * two socket ends are retained for reading and writing.  In the
 * child, the STDIN and STDOUT file descriptors refer to these
 * sockets.  In the parent, the function arguments f_in and f_out are
 * set to refer to these sockets. */
pid_t local_child(int argc, char **argv, int *f_in, int *f_out,
		  int (*child_main)(int, char*[]))
{
	pid_t pid;
	int to_child_pipe[2];
	int from_child_pipe[2];

	/* The parent process is always the sender for a local rsync. */
	assert(am_sender);

	if (fd_pair(to_child_pipe) < 0 || fd_pair(from_child_pipe) < 0) {
		rsyserr(FERROR, errno, "pipe");
		exit_cleanup(RERR_IPC);
	}

	pid = do_fork();
	if (pid == -1) {
		rsyserr(FERROR, errno, "fork");
		exit_cleanup(RERR_IPC);
	}

	if (pid == 0) {
		am_sender = 0;
		am_server = 1;
		filesfrom_fd = -1;
		munge_symlinks = 0; /* Each side needs its own option. */
		chmod_modes = NULL; /* Let the sending side handle this. */

		/* Let the client side handle this. */
		if (logfile_name) {
			logfile_name = NULL;
			logfile_close();
		}

		if (remote_option_cnt) {
			int rc = remote_option_cnt + 1;
			const char **rv = remote_options;
			if (!parse_arguments(&rc, &rv)) {
				option_error();
				exit_cleanup(RERR_SYNTAX);
			}
		}

		if (dup2(to_child_pipe[0], STDIN_FILENO) < 0
		 || close(to_child_pipe[1]) < 0
		 || close(from_child_pipe[0]) < 0
		 || dup2(from_child_pipe[1], STDOUT_FILENO) < 0) {
			rsyserr(FERROR, errno, "Failed to dup/close");
			exit_cleanup(RERR_IPC);
		}
		if (to_child_pipe[0] != STDIN_FILENO)
			close(to_child_pipe[0]);
		if (from_child_pipe[1] != STDOUT_FILENO)
			close(from_child_pipe[1]);
#ifdef ICONV_CONST
		setup_iconv();
#endif
		child_main(argc, argv);
	}

	if (close(from_child_pipe[1]) < 0 || close(to_child_pipe[0]) < 0) {
		rsyserr(FERROR, errno, "Failed to close");
		exit_cleanup(RERR_IPC);
	}

	*f_in = from_child_pipe[0];
	*f_out = to_child_pipe[1];

	return pid;
}
/*
 * Routines to output progress information during a file transfer.
 *
 * Copyright (C) 1996-2000 Andrew Tridgell
 * Copyright (C) 1996 Paul Mackerras
 * Copyright (C) 2001, 2002 Martin Pool <mbp@samba.org>
 * Copyright (C) 2003-2022 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"
#include "inums.h"

extern int am_server;
extern int flist_eof;
extern int quiet;
extern int need_unsorted_flist;
extern int output_needs_newline;
extern int stdout_format_has_i;
extern struct stats stats;
extern struct file_list *cur_flist;

BOOL want_progress_now = False;

#define PROGRESS_HISTORY_SECS 5

#ifdef GETPGRP_VOID
#define GETPGRP_ARG
#else
#define GETPGRP_ARG 0
#endif

struct progress_history {
	struct timeval time;
	OFF_T ofs;
};

static struct progress_history ph_start;
static struct progress_history ph_list[PROGRESS_HISTORY_SECS];
static int newest_hpos, oldest_hpos;
static int current_file_index;

static unsigned long msdiff(struct timeval *t1, struct timeval *t2)
{
	return (t2->tv_sec - t1->tv_sec) * 1000L
	     + (t2->tv_usec - t1->tv_usec) / 1000;
}


/**
 * @param ofs Current position in file
 * @param size Total size of file
 * @param is_last True if this is the last time progress will be
 * printed for this file, so we should output a newline.  (Not
 * necessarily the same as all bytes being received.)
 **/
static void rprint_progress(OFF_T ofs, OFF_T size, struct timeval *now, int is_last)
{
	char rembuf[64], eol[128];
	const char *units;
	unsigned long diff;
	double rate, remain;
	int pct;

	if (is_last) {
		int len = snprintf(eol, sizeof eol,
			" (xfr#%d, %s-chk=%d/%d)\n",
			stats.xferred_files, flist_eof ? "to" : "ir",
			stats.num_files - current_file_index - 1,
			stats.num_files);
		if (INFO_GTE(PROGRESS, 2)) {
			static int last_len = 0;
			/* Drop \n and pad with spaces if line got shorter. */
			if (last_len < --len)
				last_len = len;
			eol[last_len] = '\0';
			while (last_len > len)
				eol[--last_len] = ' ';
			is_last = 0;
		}
		/* Compute stats based on the starting info. */
		if (!ph_start.time.tv_sec || !(diff = msdiff(&ph_start.time, now)))
			diff = 1;
		rate = (double) (ofs - ph_start.ofs) * 1000.0 / diff / 1024.0;
		/* Switch to total time taken for our last update. */
		remain = (double) diff / 1000.0;
	} else {
		strlcpy(eol, "  ", sizeof eol);
		/* Compute stats based on recent progress. */
		if (!(diff = msdiff(&ph_list[oldest_hpos].time, now)))
			diff = 1;
		rate = (double) (ofs - ph_list[oldest_hpos].ofs) * 1000.0 / diff / 1024.0;
		remain = rate ? (double) (size - ofs) / rate / 1000.0 : 0.0;
	}

	if (rate > 1024*1024) {
		rate /= 1024.0 * 1024.0;
		units = "GB/s";
	} else if (rate > 1024) {
		rate /= 1024.0;
		units = "MB/s";
	} else {
		units = "kB/s";
	}

	if (remain < 0 || remain > 9999.0 * 3600.0)
		strlcpy(rembuf, "  ??:??:??", sizeof rembuf);
	else {
		snprintf(rembuf, sizeof rembuf, "%4u:%02u:%02u",
			 (unsigned int) (remain / 3600.0),
			 (unsigned int) (remain / 60.0) % 60,
			 (unsigned int) remain % 60);
	}

	output_needs_newline = 0;
	pct = ofs == size ? 100 : (int) (100.0 * ofs / size);
	rprintf(FCLIENT, "\r%15s %3d%% %7.2f%s %s%s",
		human_num(ofs), pct, rate, units, rembuf, eol);
	if (!is_last && !quiet) {
		output_needs_newline = 1;
		rflush(FCLIENT);
	}
}

void progress_init(void)
{
	if (!am_server && !INFO_GTE(PROGRESS, 1)) {
		struct timeval now;
		gettimeofday(&now, NULL);
		ph_start.time.tv_sec = now.tv_sec;
		ph_start.time.tv_usec = now.tv_usec;
	}
}

void set_current_file_index(struct file_struct *file, int ndx)
{
	if (!file)
		current_file_index = cur_flist->used + cur_flist->ndx_start - 1;
	else if (need_unsorted_flist)
		current_file_index = flist_find(cur_flist, file) + cur_flist->ndx_start;
	else
		current_file_index = ndx;
	current_file_index -= cur_flist->flist_num;
}

void instant_progress(const char *fname)
{
	/* We only get here if want_progress_now is True */
	if (!stdout_format_has_i && !INFO_GTE(NAME, 1))
		rprintf(FINFO, "%s\n", fname);
	end_progress(0);
	want_progress_now = False;
}

void end_progress(OFF_T size)
{
	if (!am_server) {
		struct timeval now;
		gettimeofday(&now, NULL);
		if (INFO_GTE(PROGRESS, 2) || want_progress_now) {
			rprint_progress(stats.total_transferred_size,
					stats.total_size, &now, True);
		} else {
			rprint_progress(size, size, &now, True);
			memset(&ph_start, 0, sizeof ph_start);
		}
	}
}

void show_progress(OFF_T ofs, OFF_T size)
{
	struct timeval now;
#if defined HAVE_GETPGRP && defined HAVE_TCGETPGRP
	static pid_t pgrp = -1;
	pid_t tc_pgrp;
#endif

	if (am_server)
		return;

#if defined HAVE_GETPGRP && defined HAVE_TCGETPGRP
	if (pgrp == -1)
		pgrp = getpgrp(GETPGRP_ARG);
#endif

	gettimeofday(&now, NULL);

	if (INFO_GTE(PROGRESS, 2)) {
		ofs = stats.total_transferred_size - size + ofs;
		size = stats.total_size;
	}

	if (!ph_start.time.tv_sec) {
		int i;

		/* Try to guess the real starting time when the sender started
		 * to send us data by using the time we last received some data
		 * in the last file (if it was recent enough). */
		if (msdiff(&ph_list[newest_hpos].time, &now) <= 1500) {
			ph_start.time = ph_list[newest_hpos].time;
			ph_start.ofs = 0;
		} else {
			ph_start.time.tv_sec = now.tv_sec;
			ph_start.time.tv_usec = now.tv_usec;
			ph_start.ofs = ofs;
		}

		for (i = 0; i < PROGRESS_HISTORY_SECS; i++)
			ph_list[i] = ph_start;
	}
	else {
		if (msdiff(&ph_list[newest_hpos].time, &now) < 1000)
			return;

		newest_hpos = oldest_hpos;
		oldest_hpos = (oldest_hpos + 1) % PROGRESS_HISTORY_SECS;
		ph_list[newest_hpos].time.tv_sec = now.tv_sec;
		ph_list[newest_hpos].time.tv_usec = now.tv_usec;
		ph_list[newest_hpos].ofs = ofs;
	}

#if defined HAVE_GETPGRP && defined HAVE_TCGETPGRP
	tc_pgrp = tcgetpgrp(STDOUT_FILENO);
	if (tc_pgrp != pgrp && tc_pgrp != -1)
		return;
#endif

	rprint_progress(ofs, size, &now, False);
}
/*
 * Routines only used by the receiving process.
 *
 * Copyright (C) 1996-2000 Andrew Tridgell
 * Copyright (C) 1996 Paul Mackerras
 * Copyright (C) 2003-2023 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"
#include "inums.h"

extern int dry_run;
extern int do_xfers;
extern int am_root;
extern int am_server;
extern int inc_recurse;
extern int log_before_transfer;
extern int stdout_format_has_i;
extern int logfile_format_has_i;
extern int want_xattr_optim;
extern int csum_length;
extern int read_batch;
extern int write_batch;
extern int batch_gen_fd;
extern int protocol_version;
extern int relative_paths;
extern int preserve_hard_links;
extern int preserve_perms;
extern int write_devices;
extern int preserve_xattrs;
extern int do_fsync;
extern int basis_dir_cnt;
extern int make_backups;
extern int cleanup_got_literal;
extern int remove_source_files;
extern int append_mode;
extern int sparse_files;
extern int preallocate_files;
extern int keep_partial;
extern int checksum_seed;
extern int whole_file;
extern int inplace;
extern int inplace_partial;
extern int allowed_lull;
extern int delay_updates;
extern BOOL want_progress_now;
extern mode_t orig_umask;
extern struct stats stats;
extern char *tmpdir;
extern char *partial_dir;
extern char *basis_dir[MAX_BASIS_DIRS+1];
extern char sender_file_sum[MAX_DIGEST_LEN];
extern struct file_list *cur_flist, *first_flist, *dir_flist;
extern filter_rule_list daemon_filter_list;
extern OFF_T preallocated_len;
extern int fuzzy_basis;

extern struct name_num_item *xfer_sum_nni;
extern int xfer_sum_len;

static struct bitbag *delayed_bits = NULL;
static int phase = 0, redoing = 0;
static flist_ndx_list batch_redo_list;
/* This is non-0 when we are updating the basis file or an identical copy: */
static int updating_basis_or_equiv;

#define TMPNAME_SUFFIX ".XXXXXX"
#define TMPNAME_SUFFIX_LEN ((int)sizeof TMPNAME_SUFFIX - 1)
#define MAX_UNIQUE_NUMBER 999999
#define MAX_UNIQUE_LOOP 100

/* get_tmpname() - create a tmp filename for a given filename
 *
 * If a tmpdir is defined, use that as the directory to put it in.  Otherwise,
 * the tmp filename is in the same directory as the given name.  Note that
 * there may be no directory at all in the given name!
 *
 * The tmp filename is basically the given filename with a dot prepended, and
 * .XXXXXX appended (for mkstemp() to put its unique gunk in).  We take care
 * to not exceed either the MAXPATHLEN or NAME_MAX, especially the last, as
 * the basename basically becomes 8 characters longer.  In such a case, the
 * original name is shortened sufficiently to make it all fit.
 *
 * If the make_unique arg is True, the XXXXXX string is replaced with a unique
 * string that doesn't exist at the time of the check.  This is intended to be
 * used for creating hard links, symlinks, devices, and special files, since
 * normal files should be handled by mkstemp() for safety.
 *
 * Of course, the only reason the file is based on the original name is to
 * make it easier to figure out what purpose a temp file is serving when a
 * transfer is in progress. */
int get_tmpname(char *fnametmp, const char *fname, BOOL make_unique)
{
	int maxname, length = 0;
	const char *f;
	char *suf;

	if (tmpdir) {
		/* Note: this can't overflow, so the return value is safe */
		length = strlcpy(fnametmp, tmpdir, MAXPATHLEN - 2);
		fnametmp[length++] = '/';
	}

	if ((f = strrchr(fname, '/')) != NULL) {
		++f;
		if (!tmpdir) {
			length = f - fname;
			/* copy up to and including the slash */
			strlcpy(fnametmp, fname, length + 1);
		}
	} else
		f = fname;

	if (!tmpdir) { /* using a tmpdir avoids the leading dot on our temp names */
		if (*f == '.') /* avoid an extra leading dot for OS X's sake */
			f++;
		fnametmp[length++] = '.';
	}

	/* The maxname value is bufsize, and includes space for the '\0'.
	 * NAME_MAX needs an extra -1 for the name's leading dot. */
	maxname = MIN(MAXPATHLEN - length - TMPNAME_SUFFIX_LEN,
		      NAME_MAX - 1 - TMPNAME_SUFFIX_LEN);

	if (maxname < 0) {
		rprintf(FERROR_XFER, "temporary filename too long: %s\n", fname);
		fnametmp[0] = '\0';
		return 0;
	}

	if (maxname) {
		int added = strlcpy(fnametmp + length, f, maxname);
		if (added >= maxname)
			added = maxname - 1;
		suf = fnametmp + length + added;

		/* Trim any dangling high-bit chars if the first-trimmed char (if any) is
		 * also a high-bit char, just in case we cut into a multi-byte sequence.
		 * We are guaranteed to stop because of the leading '.' we added. */
		if ((int)f[added] & 0x80) {
			while ((int)suf[-1] & 0x80)
				suf--;
		}
		/* trim one trailing dot before our suffix's dot */
		if (suf[-1] == '.')
			suf--;
	} else
		suf = fnametmp + length - 1; /* overwrite the leading dot with suffix's dot */

	if (make_unique) {
		static unsigned counter_limit;
		unsigned counter;

		if (!counter_limit) {
			counter_limit = (unsigned)getpid() + MAX_UNIQUE_LOOP;
			if (counter_limit > MAX_UNIQUE_NUMBER || counter_limit < MAX_UNIQUE_LOOP)
				counter_limit = MAX_UNIQUE_LOOP;
		}
		counter = counter_limit - MAX_UNIQUE_LOOP;

		/* This doesn't have to be very good because we don't need
		 * to worry about someone trying to guess the values:  all
		 * a conflict will do is cause a device, special file, hard
		 * link, or symlink to fail to be created.  Also: avoid
		 * using mktemp() due to gcc's annoying warning. */
		while (1) {
			snprintf(suf, TMPNAME_SUFFIX_LEN+1, ".%d", counter);
			if (access(fnametmp, 0) < 0)
				break;
			if (++counter >= counter_limit)
				return 0;
		}
	} else
		memcpy(suf, TMPNAME_SUFFIX, TMPNAME_SUFFIX_LEN+1);

	return 1;
}

/* Opens a temporary file for writing.
 * Success: Writes name into fnametmp, returns fd.
 * Failure: Clobbers fnametmp, returns -1.
 * Calling cleanup_set() is the caller's job. */
int open_tmpfile(char *fnametmp, const char *fname, struct file_struct *file)
{
	int fd;
	mode_t added_perms;

	if (!get_tmpname(fnametmp, fname, False))
		return -1;

	if (am_root < 0) {
		/* For --fake-super, the file must be useable by the copying
		 * user, just like it would be for root. */
		added_perms = S_IRUSR|S_IWUSR;
	} else {
		/* For a normal copy, we need to be able to tweak things like xattrs. */
		added_perms = S_IWUSR;
	}

	/* We initially set the perms without the setuid/setgid bits or group
	 * access to ensure that there is no race condition.  They will be
	 * correctly updated after the right owner and group info is set.
	 * (Thanks to snabb@epipe.fi for pointing this out.) */
	fd = do_mkstemp(fnametmp, (file->mode|added_perms) & INITACCESSPERMS);

#if 0
	/* In most cases parent directories will already exist because their
	 * information should have been previously transferred, but that may
	 * not be the case with -R */
	if (fd == -1 && relative_paths && errno == ENOENT
	 && make_path(fnametmp, MKP_SKIP_SLASH | MKP_DROP_NAME) == 0) {
		/* Get back to name with XXXXXX in it. */
		get_tmpname(fnametmp, fname, False);
		fd = do_mkstemp(fnametmp, (file->mode|added_perms) & INITACCESSPERMS);
	}
#endif

	if (fd == -1) {
		rsyserr(FERROR_XFER, errno, "mkstemp %s failed",
			full_fname(fnametmp));
		return -1;
	}

	return fd;
}

static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
			const char *fname, int fd, struct file_struct *file, int inplace_sizing)
{
	static char file_sum1[MAX_DIGEST_LEN];
	struct map_struct *mapbuf;
	struct sum_struct sum;
	int32 len;
	OFF_T total_size = F_LENGTH(file);
	OFF_T offset = 0;
	OFF_T offset2;
	char *data;
	int32 i;
	char *map = NULL;

#ifdef SUPPORT_PREALLOCATION
	if (preallocate_files && fd != -1 && total_size > 0 && (!inplace_sizing || total_size > size_r)) {
		/* Try to preallocate enough space for file's eventual length.  Can
		 * reduce fragmentation on filesystems like ext4, xfs, and NTFS. */
		if ((preallocated_len = do_fallocate(fd, 0, total_size)) < 0)
			rsyserr(FWARNING, errno, "do_fallocate %s", full_fname(fname));
	} else
#endif
	if (inplace_sizing) {
#ifdef HAVE_FTRUNCATE
		/* The most compatible way to create a sparse file is to start with no length. */
		if (sparse_files > 0 && whole_file && fd >= 0 && do_ftruncate(fd, 0) == 0)
			preallocated_len = 0;
		else
#endif
			preallocated_len = size_r;
	} else
		preallocated_len = 0;

	read_sum_head(f_in, &sum);

	if (fd_r >= 0 && size_r > 0) {
		int32 read_size = MAX(sum.blength * 2, 16*1024);
		mapbuf = map_file(fd_r, size_r, read_size, sum.blength);
		if (DEBUG_GTE(DELTASUM, 2)) {
			rprintf(FINFO, "recv mapped %s of size %s\n",
				fname_r, big_num(size_r));
		}
	} else
		mapbuf = NULL;

	sum_init(xfer_sum_nni, checksum_seed);

	if (append_mode > 0) {
		OFF_T j;
		sum.flength = (OFF_T)sum.count * sum.blength;
		if (sum.remainder)
			sum.flength -= sum.blength - sum.remainder;
		if (append_mode == 2 && mapbuf) {
			for (j = CHUNK_SIZE; j < sum.flength; j += CHUNK_SIZE) {
				if (INFO_GTE(PROGRESS, 1))
					show_progress(offset, total_size);
				sum_update(map_ptr(mapbuf, offset, CHUNK_SIZE),
					   CHUNK_SIZE);
				offset = j;
			}
			if (offset < sum.flength) {
				int32 len = (int32)(sum.flength - offset);
				if (INFO_GTE(PROGRESS, 1))
					show_progress(offset, total_size);
				sum_update(map_ptr(mapbuf, offset, len), len);
			}
		}
		offset = sum.flength;
		if (fd != -1 && (j = do_lseek(fd, offset, SEEK_SET)) != offset) {
			rsyserr(FERROR_XFER, errno, "lseek of %s returned %s, not %s",
				full_fname(fname), big_num(j), big_num(offset));
			exit_cleanup(RERR_FILEIO);
		}
	}

	while ((i = recv_token(f_in, &data)) != 0) {
		if (INFO_GTE(PROGRESS, 1))
			show_progress(offset, total_size);

		if (allowed_lull)
			maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH | MSK_ACTIVE_RECEIVER);

		if (i > 0) {
			if (DEBUG_GTE(DELTASUM, 3)) {
				rprintf(FINFO,"data recv %d at %s\n",
					i, big_num(offset));
			}

			stats.literal_data += i;
			cleanup_got_literal = 1;

			sum_update(data, i);

			if (fd != -1 && write_file(fd, 0, offset, data, i) != i)
				goto report_write_error;
			offset += i;
			continue;
		}

		i = -(i+1);
		offset2 = i * (OFF_T)sum.blength;
		len = sum.blength;
		if (i == (int)sum.count-1 && sum.remainder != 0)
			len = sum.remainder;

		stats.matched_data += len;

		if (DEBUG_GTE(DELTASUM, 3)) {
			rprintf(FINFO,
				"chunk[%d] of size %ld at %s offset=%s%s\n",
				i, (long)len, big_num(offset2), big_num(offset),
				updating_basis_or_equiv && offset == offset2 ? " (seek)" : "");
		}

		if (mapbuf) {
			map = map_ptr(mapbuf,offset2,len);

			see_token(map, len);
			sum_update(map, len);
		}

		if (updating_basis_or_equiv) {
			if (offset == offset2 && fd != -1) {
				if (skip_matched(fd, offset, map, len) < 0)
					goto report_write_error;
				offset += len;
				continue;
			}
		}
		if (fd != -1 && map && write_file(fd, 0, offset, map, len) != (int)len)
			goto report_write_error;
		offset += len;
	}

	if (fd != -1 && offset > 0) {
		if (sparse_files > 0) {
			if (sparse_end(fd, offset, updating_basis_or_equiv) != 0)
				goto report_write_error;
		} else if (flush_write_file(fd) < 0) {
		    report_write_error:
			rsyserr(FERROR_XFER, errno, "write failed on %s", full_fname(fname));
			exit_cleanup(RERR_FILEIO);
		}
	}

#ifdef HAVE_FTRUNCATE
	/* inplace: New data could be shorter than old data.
	 * preallocate_files: total_size could have been an overestimate.
	 *     Cut off any extra preallocated zeros from dest file. */
	if ((inplace_sizing || preallocated_len > offset) && fd != -1 && !IS_DEVICE(file->mode)) {
		if (do_ftruncate(fd, offset) < 0)
			rsyserr(FERROR_XFER, errno, "ftruncate failed on %s", full_fname(fname));
	}
#endif

	if (INFO_GTE(PROGRESS, 1))
		end_progress(total_size);

	sum_end(file_sum1);

	if (do_fsync && fd != -1 && fsync(fd) != 0) {
		rsyserr(FERROR, errno, "fsync failed on %s", full_fname(fname));
		exit_cleanup(RERR_FILEIO);
	}

	if (mapbuf)
		unmap_file(mapbuf);

	read_buf(f_in, sender_file_sum, xfer_sum_len);
	if (DEBUG_GTE(DELTASUM, 2))
		rprintf(FINFO,"got file_sum\n");
	if (fd != -1 && memcmp(file_sum1, sender_file_sum, xfer_sum_len) != 0)
		return 0;
	return 1;
}


static void discard_receive_data(int f_in, struct file_struct *file)
{
	receive_data(f_in, NULL, -1, 0, NULL, -1, file, 0);
}

static void handle_delayed_updates(char *local_name)
{
	char *fname, *partialptr;
	int ndx;

	for (ndx = -1; (ndx = bitbag_next_bit(delayed_bits, ndx)) >= 0; ) {
		struct file_struct *file = cur_flist->files[ndx];
		fname = local_name ? local_name : f_name(file, NULL);
		if ((partialptr = partial_dir_fname(fname)) != NULL) {
			if (make_backups > 0 && !make_backup(fname, False))
				continue;
			if (DEBUG_GTE(RECV, 1)) {
				rprintf(FINFO, "renaming %s to %s\n",
					partialptr, fname);
			}
			/* We don't use robust_rename() here because the
			 * partial-dir must be on the same drive. */
			if (do_rename(partialptr, fname) < 0) {
				rsyserr(FERROR_XFER, errno,
					"rename failed for %s (from %s)",
					full_fname(fname), partialptr);
			} else {
				if (remove_source_files || (preserve_hard_links && F_IS_HLINKED(file)))
					send_msg_success(fname, ndx);
				handle_partial_dir(partialptr, PDIR_DELETE);
			}
		}
	}
}

static void no_batched_update(int ndx, BOOL is_redo)
{
	struct file_list *flist = flist_for_ndx(ndx, "no_batched_update");
	struct file_struct *file = flist->files[ndx - flist->ndx_start];

	rprintf(FERROR_XFER, "(No batched update for%s \"%s\")\n",
		is_redo ? " resend of" : "", f_name(file, NULL));

	if (inc_recurse && !dry_run)
		send_msg_int(MSG_NO_SEND, ndx);
}

static int we_want_redo(int desired_ndx)
{
	static int redo_ndx = -1;

	while (redo_ndx < desired_ndx) {
		if (redo_ndx >= 0)
			no_batched_update(redo_ndx, True);
		if ((redo_ndx = flist_ndx_pop(&batch_redo_list)) < 0)
			return 0;
	}

	if (redo_ndx == desired_ndx) {
		redo_ndx = -1;
		return 1;
	}

	return 0;
}

static int gen_wants_ndx(int desired_ndx, int flist_num)
{
	static int next_ndx = -1;
	static int done_cnt = 0;
	static BOOL got_eof = False;

	if (got_eof)
		return 0;

	/* TODO: integrate gen-reading I/O into perform_io() so this is not needed? */
	io_flush(FULL_FLUSH);

	while (next_ndx < desired_ndx) {
		if (inc_recurse && flist_num <= done_cnt)
			return 0;
		if (next_ndx >= 0)
			no_batched_update(next_ndx, False);
		if ((next_ndx = read_int(batch_gen_fd)) < 0) {
			if (inc_recurse) {
				done_cnt++;
				continue;
			}
			got_eof = True;
			return 0;
		}
	}

	if (next_ndx == desired_ndx) {
		next_ndx = -1;
		return 1;
	}

	return 0;
}

/**
 * main routine for receiver process.
 *
 * Receiver process runs on the same host as the generator process. */
int recv_files(int f_in, int f_out, char *local_name)
{
	int fd1,fd2;
	STRUCT_STAT st;
	int iflags, xlen;
	char *fname, fbuf[MAXPATHLEN];
	char xname[MAXPATHLEN];
	char *fnametmp, fnametmpbuf[MAXPATHLEN];
	char *fnamecmp, *partialptr;
	char fnamecmpbuf[MAXPATHLEN];
	uchar fnamecmp_type;
	struct file_struct *file;
	int itemizing = am_server ? logfile_format_has_i : stdout_format_has_i;
	enum logcode log_code = log_before_transfer ? FLOG : FINFO;
	int max_phase = protocol_version >= 29 ? 2 : 1;
	int dflt_perms = (ACCESSPERMS & ~orig_umask);
#ifdef SUPPORT_ACLS
	const char *parent_dirname = "";
#endif
	int ndx, recv_ok, one_inplace;

	if (DEBUG_GTE(RECV, 1))
		rprintf(FINFO, "recv_files(%d) starting\n", cur_flist->used);

	if (delay_updates)
		delayed_bits = bitbag_create(cur_flist->used + 1);

	if (whole_file < 0)
		whole_file = 0;

	progress_init();

	while (1) {
		const char *basedir = NULL;

		cleanup_disable();

		/* This call also sets cur_flist. */
		ndx = read_ndx_and_attrs(f_in, f_out, &iflags, &fnamecmp_type,
					 xname, &xlen);
		if (ndx == NDX_DONE) {
			if (!am_server && cur_flist) {
				set_current_file_index(NULL, 0);
				if (INFO_GTE(PROGRESS, 2))
					end_progress(0);
			}
			if (inc_recurse && first_flist) {
				if (read_batch) {
					ndx = first_flist->used + first_flist->ndx_start;
					gen_wants_ndx(ndx, first_flist->flist_num);
				}
				flist_free(first_flist);
				if (first_flist)
					continue;
			} else if (read_batch && first_flist) {
				ndx = first_flist->used;
				gen_wants_ndx(ndx, first_flist->flist_num);
			}
			if (++phase > max_phase)
				break;
			if (DEBUG_GTE(RECV, 1))
				rprintf(FINFO, "recv_files phase=%d\n", phase);
			if (phase == 2 && delay_updates)
				handle_delayed_updates(local_name);
			write_int(f_out, NDX_DONE);
			continue;
		}

		if (ndx - cur_flist->ndx_start >= 0)
			file = cur_flist->files[ndx - cur_flist->ndx_start];
		else
			file = dir_flist->files[cur_flist->parent_ndx];
		fname = local_name ? local_name : f_name(file, fbuf);

		if (DEBUG_GTE(RECV, 1))
			rprintf(FINFO, "recv_files(%s)\n", fname);

		if (daemon_filter_list.head && (*fname != '.' || fname[1] != '\0')) {
			int filt_flags = S_ISDIR(file->mode) ? NAME_IS_DIR : NAME_IS_FILE;
			if (check_filter(&daemon_filter_list, FLOG, fname, filt_flags) < 0) {
				rprintf(FERROR, "ERROR: rejecting file transfer request for daemon excluded file: %s\n",
					fname);
				exit_cleanup(RERR_PROTOCOL);
			}
		}

#ifdef SUPPORT_XATTRS
		if (preserve_xattrs && iflags & ITEM_REPORT_XATTR && do_xfers
		 && !(want_xattr_optim && BITS_SET(iflags, ITEM_XNAME_FOLLOWS|ITEM_LOCAL_CHANGE)))
			recv_xattr_request(file, f_in);
#endif

		if (!(iflags & ITEM_TRANSFER)) {
			maybe_log_item(file, iflags, itemizing, xname);
#ifdef SUPPORT_XATTRS
			if (preserve_xattrs && iflags & ITEM_REPORT_XATTR && do_xfers
			 && !BITS_SET(iflags, ITEM_XNAME_FOLLOWS|ITEM_LOCAL_CHANGE))
				set_file_attrs(fname, file, NULL, fname, 0);
#endif
			if (iflags & ITEM_IS_NEW) {
				stats.created_files++;
				if (S_ISREG(file->mode)) {
					/* Nothing further to count. */
				} else if (S_ISDIR(file->mode))
					stats.created_dirs++;
#ifdef SUPPORT_LINKS
				else if (S_ISLNK(file->mode))
					stats.created_symlinks++;
#endif
				else if (IS_DEVICE(file->mode))
					stats.created_devices++;
				else
					stats.created_specials++;
			}
			continue;
		}
		if (phase == 2) {
			rprintf(FERROR,
				"got transfer request in phase 2 [%s]\n",
				who_am_i());
			exit_cleanup(RERR_PROTOCOL);
		}

		if (file->flags & FLAG_FILE_SENT) {
			if (csum_length == SHORT_SUM_LENGTH) {
				if (keep_partial && !partial_dir)
					make_backups = -make_backups; /* prevents double backup */
				if (append_mode)
					sparse_files = -sparse_files;
				append_mode = -append_mode;
				csum_length = SUM_LENGTH;
				redoing = 1;
			}
		} else {
			if (csum_length != SHORT_SUM_LENGTH) {
				if (keep_partial && !partial_dir)
					make_backups = -make_backups;
				if (append_mode)
					sparse_files = -sparse_files;
				append_mode = -append_mode;
				csum_length = SHORT_SUM_LENGTH;
				redoing = 0;
			}
			if (iflags & ITEM_IS_NEW)
				stats.created_files++;
		}

		if (!am_server)
			set_current_file_index(file, ndx);
		stats.xferred_files++;
		stats.total_transferred_size += F_LENGTH(file);

		cleanup_got_literal = 0;

		if (read_batch) {
			int wanted = redoing
				   ? we_want_redo(ndx)
				   : gen_wants_ndx(ndx, cur_flist->flist_num);
			if (!wanted) {
				rprintf(FINFO,
					"(Skipping batched update for%s \"%s\")\n",
					redoing ? " resend of" : "",
					fname);
				discard_receive_data(f_in, file);
				file->flags |= FLAG_FILE_SENT;
				continue;
			}
		}

		remember_initial_stats();

		if (!do_xfers) { /* log the transfer */
			log_item(FCLIENT, file, iflags, NULL);
			if (read_batch)
				discard_receive_data(f_in, file);
			continue;
		}
		if (write_batch < 0) {
			log_item(FCLIENT, file, iflags, NULL);
			if (!am_server)
				discard_receive_data(f_in, file);
			if (inc_recurse)
				send_msg_success(fname, ndx);
			continue;
		}

		partialptr = partial_dir ? partial_dir_fname(fname) : fname;

		if (protocol_version >= 29) {
			switch (fnamecmp_type) {
			case FNAMECMP_FNAME:
				fnamecmp = fname;
				break;
			case FNAMECMP_PARTIAL_DIR:
				fnamecmp = partialptr;
				break;
			case FNAMECMP_BACKUP:
				fnamecmp = get_backup_name(fname);
				break;
			case FNAMECMP_FUZZY:
				if (fuzzy_basis == 0) {
					rprintf(FERROR_XFER, "rsync: refusing malicious fuzzy operation for %s\n", xname);
					exit_cleanup(RERR_PROTOCOL);
				}
				if (file->dirname) {
					basedir = file->dirname;
				}
				fnamecmp = xname;
				break;
			default:
				if (fnamecmp_type > FNAMECMP_FUZZY && fnamecmp_type-FNAMECMP_FUZZY <= basis_dir_cnt) {
					fnamecmp_type -= FNAMECMP_FUZZY + 1;
					if (file->dirname) {
						pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, basis_dir[fnamecmp_type], file->dirname);
						basedir = fnamecmpbuf;
					} else {
						basedir = basis_dir[fnamecmp_type];
					}
					fnamecmp = xname;
				} else if (fnamecmp_type >= basis_dir_cnt) {
					rprintf(FERROR,
						"invalid basis_dir index: %d.\n",
						fnamecmp_type);
					exit_cleanup(RERR_PROTOCOL);
				} else {
					basedir = basis_dir[fnamecmp_type];
					fnamecmp = fname;
				}
				break;
			}
			if (!fnamecmp || (daemon_filter_list.head
			  && check_filter(&daemon_filter_list, FLOG, fnamecmp, 0) < 0)) {
				fnamecmp = fname;
				fnamecmp_type = FNAMECMP_FNAME;
			}
		} else {
			/* Reminder: --inplace && --partial-dir are never
			 * enabled at the same time. */
			if (inplace && make_backups > 0) {
				if (!(fnamecmp = get_backup_name(fname)))
					fnamecmp = fname;
				else
					fnamecmp_type = FNAMECMP_BACKUP;
			} else if (partial_dir && partialptr)
				fnamecmp = partialptr;
			else
				fnamecmp = fname;
		}

		/* open the file */
		fd1 = secure_relative_open(basedir, fnamecmp, O_RDONLY, 0);

		if (fd1 == -1 && protocol_version < 29) {
			if (fnamecmp != fname) {
				fnamecmp = fname;
				fnamecmp_type = FNAMECMP_FNAME;
				fd1 = do_open_nofollow(fnamecmp, O_RDONLY);
			}

			if (fd1 == -1 && basis_dir[0]) {
				/* pre-29 allowed only one alternate basis */
				basedir = basis_dir[0];
				fnamecmp = fname;
				fnamecmp_type = FNAMECMP_BASIS_DIR_LOW;
				fd1 = secure_relative_open(basedir, fnamecmp, O_RDONLY, 0);
			}
		}

		if (basedir) {
			// for the following code we need the full
			// path name as a single string
			pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, basedir, fnamecmp);
			fnamecmp = fnamecmpbuf;
		}

		one_inplace = inplace_partial && fnamecmp_type == FNAMECMP_PARTIAL_DIR;
		updating_basis_or_equiv = one_inplace
		    || (inplace && (fnamecmp == fname || fnamecmp_type == FNAMECMP_BACKUP));

		if (fd1 == -1) {
			st.st_mode = 0;
			st.st_size = 0;
		} else if (do_fstat(fd1,&st) != 0) {
			rsyserr(FERROR_XFER, errno, "fstat %s failed",
				full_fname(fnamecmp));
			discard_receive_data(f_in, file);
			close(fd1);
			if (inc_recurse)
				send_msg_int(MSG_NO_SEND, ndx);
			continue;
		}

		if (fd1 != -1 && S_ISDIR(st.st_mode) && fnamecmp == fname) {
			/* this special handling for directories
			 * wouldn't be necessary if robust_rename()
			 * and the underlying robust_unlink could cope
			 * with directories
			 */
			rprintf(FERROR_XFER, "recv_files: %s is a directory\n",
				full_fname(fnamecmp));
			discard_receive_data(f_in, file);
			close(fd1);
			if (inc_recurse)
				send_msg_int(MSG_NO_SEND, ndx);
			continue;
		}

		if (write_devices && IS_DEVICE(st.st_mode)) {
			if (fd1 != -1 && st.st_size == 0)
				st.st_size = get_device_size(fd1, fname);
			/* Mark the file entry as a device so that we don't try to truncate it later on. */
			file->mode = S_IFBLK | (file->mode & ACCESSPERMS);
		} else if (fd1 != -1 && !(S_ISREG(st.st_mode))) {
			close(fd1);
			fd1 = -1;
		}

		/* If we're not preserving permissions, change the file-list's
		 * mode based on the local permissions and some heuristics. */
		if (!preserve_perms) {
			int exists = fd1 != -1;
#ifdef SUPPORT_ACLS
			const char *dn = file->dirname ? file->dirname : ".";
			if (parent_dirname != dn
			 && strcmp(parent_dirname, dn) != 0) {
				dflt_perms = default_perms_for_dir(dn);
				parent_dirname = dn;
			}
#endif
			file->mode = dest_mode(file->mode, st.st_mode, dflt_perms, exists);
		}

		/* We now check to see if we are writing the file "inplace" */
		if (inplace || one_inplace)  {
			fnametmp = one_inplace ? partialptr : fname;
			fd2 = do_open(fnametmp, O_WRONLY|O_CREAT, 0600);
#ifdef linux
			if (fd2 == -1 && errno == EACCES) {
				/* Maybe the error was due to protected_regular setting? */
				fd2 = do_open(fname, O_WRONLY, 0600);
			}
#endif
			if (fd2 == -1) {
				rsyserr(FERROR_XFER, errno, "open %s failed",
					full_fname(fnametmp));
			} else if (updating_basis_or_equiv)
				cleanup_set(NULL, NULL, file, fd1, fd2);
		} else {
			fnametmp = fnametmpbuf;
			fd2 = open_tmpfile(fnametmp, fname, file);
			if (fd2 != -1)
				cleanup_set(fnametmp, partialptr, file, fd1, fd2);
		}

		if (fd2 == -1) {
			discard_receive_data(f_in, file);
			if (fd1 != -1)
				close(fd1);
			if (inc_recurse)
				send_msg_int(MSG_NO_SEND, ndx);
			continue;
		}

		/* log the transfer */
		if (log_before_transfer)
			log_item(FCLIENT, file, iflags, NULL);
		else if (!am_server && INFO_GTE(NAME, 1) && INFO_EQ(PROGRESS, 1))
			rprintf(FINFO, "%s\n", fname);

		/* recv file data */
		recv_ok = receive_data(f_in, fnamecmp, fd1, st.st_size, fname, fd2, file, inplace || one_inplace);

		log_item(log_code, file, iflags, NULL);
		if (want_progress_now)
			instant_progress(fname);

		if (fd1 != -1)
			close(fd1);
		if (close(fd2) < 0) {
			rsyserr(FERROR, errno, "close failed on %s",
				full_fname(fnametmp));
			exit_cleanup(RERR_FILEIO);
		}

		if ((recv_ok && (!delay_updates || !partialptr)) || inplace) {
			if (partialptr == fname)
				partialptr = NULL;
			if (!finish_transfer(fname, fnametmp, fnamecmp, partialptr, file, recv_ok, 1))
				recv_ok = -1;
			else if (fnamecmp == partialptr) {
				if (!one_inplace)
					do_unlink(partialptr);
				handle_partial_dir(partialptr, PDIR_DELETE);
			}
		} else if (keep_partial && partialptr && (!one_inplace || delay_updates)) {
			if (!handle_partial_dir(partialptr, PDIR_CREATE)) {
				rprintf(FERROR,
					"Unable to create partial-dir for %s -- discarding %s.\n",
					local_name ? local_name : f_name(file, NULL),
					recv_ok ? "completed file" : "partial file");
				do_unlink(fnametmp);
				recv_ok = -1;
			} else if (!finish_transfer(partialptr, fnametmp, fnamecmp, NULL,
						    file, recv_ok, !partial_dir))
				recv_ok = -1;
			else if (delay_updates && recv_ok) {
				bitbag_set_bit(delayed_bits, ndx);
				recv_ok = 2;
			} else
				partialptr = NULL;
		} else if (!one_inplace)
			do_unlink(fnametmp);

		cleanup_disable();

		if (read_batch)
			file->flags |= FLAG_FILE_SENT;

		switch (recv_ok) {
		case 2:
			break;
		case 1:
			if (remove_source_files || inc_recurse || (preserve_hard_links && F_IS_HLINKED(file)))
				send_msg_success(fname, ndx);
			break;
		case 0: {
			enum logcode msgtype = redoing ? FERROR_XFER : FWARNING;
			if (msgtype == FERROR_XFER || INFO_GTE(NAME, 1) || stdout_format_has_i) {
				char *errstr, *redostr, *keptstr;
				if (!(keep_partial && partialptr) && !inplace)
					keptstr = "discarded";
				else if (partial_dir)
					keptstr = "put into partial-dir";
				else
					keptstr = "retained";
				if (msgtype == FERROR_XFER) {
					errstr = "ERROR";
					redostr = "";
				} else {
					errstr = "WARNING";
					redostr = read_batch ? " (may try again)"
							     : " (will try again)";
				}
				rprintf(msgtype,
					"%s: %s failed verification -- update %s%s.\n",
					errstr, local_name ? f_name(file, NULL) : fname,
					keptstr, redostr);
			}
			if (!redoing) {
				if (read_batch)
					flist_ndx_push(&batch_redo_list, ndx);
				send_msg_int(MSG_REDO, ndx);
				file->flags |= FLAG_FILE_SENT;
			} else if (inc_recurse)
				send_msg_int(MSG_NO_SEND, ndx);
			break;
		}
		case -1:
			if (inc_recurse)
				send_msg_int(MSG_NO_SEND, ndx);
			break;
		}
	}
	if (make_backups < 0)
		make_backups = -make_backups;

	if (phase == 2 && delay_updates) /* for protocol_version < 29 */
		handle_delayed_updates(local_name);

	if (DEBUG_GTE(RECV, 1))
		rprintf(FINFO,"recv_files finished\n");

	return 0;
}
/*
 * A pre-compilation helper program to aid in the creation of rounding.h.
 *
 * Copyright (C) 2007-2020 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"

#define ARRAY_LEN (EXTRA_ROUNDING+1)
#define SIZEOF(x) ((long int)sizeof (x))

struct test {
	union file_extras extras[ARRAY_LEN];
	int64 test;
};

#define ACTUAL_SIZE	SIZEOF(struct test)
#define EXPECTED_SIZE	(SIZEOF(union file_extras) * ARRAY_LEN + SIZEOF(int64))

 int main(UNUSED(int argc), UNUSED(char *argv[]))
{
	static int test_array[1 - 2 * (ACTUAL_SIZE != EXPECTED_SIZE)];
	test_array[0] = 0;
	return 0;
}
/*
 * Routines common to more than one of the rsync processes.
 *
 * Copyright (C) 1996 Andrew Tridgell
 * Copyright (C) 1996 Paul Mackerras
 * Copyright (C) 2003-2022 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"
#include "ifuncs.h"
#if defined HAVE_LIBCHARSET_H && defined HAVE_LOCALE_CHARSET
#include <libcharset.h>
#elif defined HAVE_LANGINFO_H && defined HAVE_NL_LANGINFO
#include <langinfo.h>
#endif

extern int dry_run;
extern int preserve_acls;
extern int preserve_xattrs;
extern int preserve_perms;
extern int preserve_executability;
extern int preserve_mtimes;
extern int omit_dir_times;
extern int omit_link_times;
extern int am_root;
extern int am_server;
extern int am_daemon;
extern int am_sender;
extern int am_receiver;
extern int am_generator;
extern int am_starting_up;
extern int allow_8bit_chars;
extern int protocol_version;
extern int got_kill_signal;
extern int called_from_signal_handler;
extern int inc_recurse;
extern int inplace;
extern int flist_eof;
extern int file_old_total;
extern int keep_dirlinks;
extern int make_backups;
extern int sanitize_paths;
extern struct file_list *cur_flist, *first_flist, *dir_flist;
extern struct chmod_mode_struct *daemon_chmod_modes;
#ifdef ICONV_OPTION
extern char *iconv_opt;
#endif

#define UPDATED_OWNER (1<<0)
#define UPDATED_GROUP (1<<1)
#define UPDATED_MTIME (1<<2)
#define UPDATED_ATIME (1<<3)
#define UPDATED_ACLS  (1<<4)
#define UPDATED_MODE  (1<<5)
#define UPDATED_CRTIME (1<<6)

#ifdef ICONV_CONST
iconv_t ic_chck = (iconv_t)-1;
# ifdef ICONV_OPTION
iconv_t ic_send = (iconv_t)-1, ic_recv = (iconv_t)-1;
# endif

static const char *default_charset(void)
{
# if defined HAVE_LIBCHARSET_H && defined HAVE_LOCALE_CHARSET
	return locale_charset();
# elif defined HAVE_LANGINFO_H && defined HAVE_NL_LANGINFO
	return nl_langinfo(CODESET);
# else
	return ""; /* Works with (at the very least) gnu iconv... */
# endif
}

void setup_iconv(void)
{
	const char *defset = default_charset();
# ifdef ICONV_OPTION
	const char *charset;
	char *cp;
# endif

	if (!am_server && !allow_8bit_chars) {
		/* It's OK if this fails... */
		ic_chck = iconv_open(defset, defset);

		if (DEBUG_GTE(ICONV, 2)) {
			if (ic_chck == (iconv_t)-1) {
				rprintf(FINFO,
					"msg checking via isprint()"
					" (iconv_open(\"%s\", \"%s\") errno: %d)\n",
					defset, defset, errno);
			} else {
				rprintf(FINFO,
					"msg checking charset: %s\n",
					defset);
			}
		}
	} else
		ic_chck = (iconv_t)-1;

# ifdef ICONV_OPTION
	if (!iconv_opt)
		return;

	if ((cp = strchr(iconv_opt, ',')) != NULL) {
		if (am_server) /* A local transfer needs this. */
			iconv_opt = cp + 1;
		else
			*cp = '\0';
	}

	if (!*iconv_opt || (*iconv_opt == '.' && iconv_opt[1] == '\0'))
		charset = defset;
	else
		charset = iconv_opt;

	if ((ic_send = iconv_open(UTF8_CHARSET, charset)) == (iconv_t)-1) {
		rprintf(FERROR, "iconv_open(\"%s\", \"%s\") failed\n",
			UTF8_CHARSET, charset);
		exit_cleanup(RERR_UNSUPPORTED);
	}

	if ((ic_recv = iconv_open(charset, UTF8_CHARSET)) == (iconv_t)-1) {
		rprintf(FERROR, "iconv_open(\"%s\", \"%s\") failed\n",
			charset, UTF8_CHARSET);
		exit_cleanup(RERR_UNSUPPORTED);
	}

	if (DEBUG_GTE(ICONV, 1)) {
		rprintf(FINFO, "[%s] charset: %s\n",
			who_am_i(), *charset ? charset : "[LOCALE]");
	}
# endif
}

/* This function converts the chars in the "in" xbuf into characters in the
 * "out" xbuf.  The ".len" chars of the "in" xbuf is used starting from its
 * ".pos".  The ".size" of the "out" xbuf restricts how many characters can
 * be stored, starting at its ".pos+.len" position.  Note that the last byte
 * of the "out" xbuf is not used, which reserves space for a trailing '\0'
 * (though it is up to the caller to store a trailing '\0', as needed).
 *
 * We return a 0 on success or a -1 on error.  An error also sets errno to
 * E2BIG, EILSEQ, or EINVAL (see below); otherwise errno will be set to 0.
 * The "in" xbuf is altered to update ".pos" and ".len".  The "out" xbuf has
 * data appended, and its ".len" incremented (see below for a ".size" note).
 *
 * If ICB_CIRCULAR_OUT is set in "flags", the chars going into the "out" xbuf
 * can wrap around to the start, and the xbuf may have its ".size" reduced
 * (presumably by 1 byte) if the iconv code doesn't have space to store a
 * multi-byte character at the physical end of the ".buf" (though no reducing
 * happens if ".pos" is <= 1, since there is no room to wrap around).
 *
 * If ICB_EXPAND_OUT is set in "flags", the "out" xbuf will be allocated if
 * empty, and (as long as ICB_CIRCULAR_OUT is not set) expanded if too small.
 * This prevents the return of E2BIG (except for a circular xbuf).
 *
 * If ICB_INCLUDE_BAD is set in "flags", any badly-encoded chars are included
 * verbatim in the "out" xbuf, so EILSEQ will not be returned.
 *
 * If ICB_INCLUDE_INCOMPLETE is set in "flags", any incomplete multi-byte
 * chars are included, which ensures that EINVAL is not returned.
 *
 * If ICB_INIT is set, the iconv() conversion state is initialized prior to
 * processing the characters. */
int iconvbufs(iconv_t ic, xbuf *in, xbuf *out, int flags)
{
	ICONV_CONST char *ibuf;
	size_t icnt, ocnt, opos;
	char *obuf;

	if (!out->size && flags & ICB_EXPAND_OUT) {
		size_t siz = ROUND_UP_1024(in->len * 2);
		alloc_xbuf(out, siz);
	} else if (out->len+1 >= out->size) {
		/* There is no room to even start storing data. */
		if (!(flags & ICB_EXPAND_OUT) || flags & ICB_CIRCULAR_OUT) {
			errno = E2BIG;
			return -1;
		}
		realloc_xbuf(out, out->size + ROUND_UP_1024(in->len * 2));
	}

	if (flags & ICB_INIT)
		iconv(ic, NULL, 0, NULL, 0);

	ibuf = in->buf + in->pos;
	icnt = in->len;

	opos = out->pos + out->len;
	if (flags & ICB_CIRCULAR_OUT) {
		if (opos >= out->size) {
			opos -= out->size;
			/* We know that out->pos is not 0 due to the "no room" check
			 * above, so this can't go "negative". */
			ocnt = out->pos - opos - 1;
		} else {
			/* Allow the use of all bytes to the physical end of the buffer
			 * unless pos is 0, in which case we reserve our trailing '\0'. */
			ocnt = out->size - opos - (out->pos ? 0 : 1);
		}
	} else
		ocnt = out->size - opos - 1;
	obuf = out->buf + opos;

	while (icnt) {
		while (iconv(ic, &ibuf, &icnt, &obuf, &ocnt) == (size_t)-1) {
			if (errno == EINTR)
				continue;
			if (errno == EINVAL) {
				if (!(flags & ICB_INCLUDE_INCOMPLETE))
					goto finish;
				if (!ocnt)
					goto e2big;
			} else if (errno == EILSEQ) {
				if (!(flags & ICB_INCLUDE_BAD))
					goto finish;
				if (!ocnt)
					goto e2big;
			} else if (errno == E2BIG) {
				size_t siz;
			  e2big:
				opos = obuf - out->buf;
				if (flags & ICB_CIRCULAR_OUT && out->pos > 1 && opos > out->pos) {
					/* We are in a divided circular buffer at the physical
					 * end with room to wrap to the start.  If iconv() refused
					 * to use one or more trailing bytes in the buffer, we
					 * set the size to ignore the unused bytes. */
					if (opos < out->size)
						reduce_iobuf_size(out, opos);
					obuf = out->buf;
					ocnt = out->pos - 1;
					continue;
				}
				if (!(flags & ICB_EXPAND_OUT) || flags & ICB_CIRCULAR_OUT) {
					errno = E2BIG;
					goto finish;
				}
				siz = ROUND_UP_1024(in->len * 2);
				realloc_xbuf(out, out->size + siz);
				obuf = out->buf + opos;
				ocnt += siz;
				continue;
			} else {
				rsyserr(FERROR, errno, "unexpected error from iconv()");
				exit_cleanup(RERR_UNSUPPORTED);
			}
			*obuf++ = *ibuf++;
			ocnt--, icnt--;
			if (!icnt)
				break;
		}
	}

	errno = 0;

  finish:
	opos = obuf - out->buf;
	if (flags & ICB_CIRCULAR_OUT && opos < out->pos)
		opos += out->size;
	out->len = opos - out->pos;

	in->len = icnt;
	in->pos = ibuf - in->buf;

	return errno ? -1 : 0;
}
#endif

void send_protected_args(int fd, char *args[])
{
	int i;
#ifdef ICONV_OPTION
	int convert = ic_send != (iconv_t)-1;
	xbuf outbuf, inbuf;

	if (convert)
		alloc_xbuf(&outbuf, 1024);
#endif

	for (i = 0; args[i]; i++) {} /* find first NULL */
	args[i] = "rsync"; /* set a new arg0 */
	if (DEBUG_GTE(CMD, 1))
		print_child_argv("protected args:", args + i + 1);
	do {
		if (!args[i][0])
			write_buf(fd, ".", 2);
#ifdef ICONV_OPTION
		else if (convert) {
			INIT_XBUF_STRLEN(inbuf, args[i]);
			iconvbufs(ic_send, &inbuf, &outbuf,
				  ICB_EXPAND_OUT | ICB_INCLUDE_BAD | ICB_INCLUDE_INCOMPLETE | ICB_INIT);
			outbuf.buf[outbuf.len] = '\0';
			write_buf(fd, outbuf.buf, outbuf.len + 1);
			outbuf.len = 0;
		}
#endif
		else
			write_buf(fd, args[i], strlen(args[i]) + 1);
	} while (args[++i]);
	write_byte(fd, 0);

#ifdef ICONV_OPTION
	if (convert)
		free(outbuf.buf);
#endif
}

int read_ndx_and_attrs(int f_in, int f_out, int *iflag_ptr, uchar *type_ptr, char *buf, int *len_ptr)
{
	int len, iflags = 0;
	struct file_list *flist;
	uchar fnamecmp_type = FNAMECMP_FNAME;
	int ndx;

  read_loop:
	while (1) {
		ndx = read_ndx(f_in);

		if (ndx >= 0)
			break;
		if (ndx == NDX_DONE)
			return ndx;
		if (ndx == NDX_DEL_STATS) {
			read_del_stats(f_in);
			if (am_sender && am_server)
				write_del_stats(f_out);
			continue;
		}
		if (!inc_recurse || am_sender) {
			int last;
			if (first_flist)
				last = first_flist->prev->ndx_start + first_flist->prev->used - 1;
			else
				last = -1;
			rprintf(FERROR,
				"Invalid file index: %d (%d - %d) [%s]\n",
				ndx, NDX_DONE, last, who_am_i());
			exit_cleanup(RERR_PROTOCOL);
		}
		if (ndx == NDX_FLIST_EOF) {
			flist_eof = 1;
			if (DEBUG_GTE(FLIST, 3))
				rprintf(FINFO, "[%s] flist_eof=1\n", who_am_i());
			write_int(f_out, NDX_FLIST_EOF);
			continue;
		}
		ndx = NDX_FLIST_OFFSET - ndx;
		if (ndx < 0 || ndx >= dir_flist->used) {
			ndx = NDX_FLIST_OFFSET - ndx;
			rprintf(FERROR,
				"Invalid dir index: %d (%d - %d) [%s]\n",
				ndx, NDX_FLIST_OFFSET,
				NDX_FLIST_OFFSET - dir_flist->used + 1,
				who_am_i());
			exit_cleanup(RERR_PROTOCOL);
		}

		if (DEBUG_GTE(FLIST, 2)) {
			rprintf(FINFO, "[%s] receiving flist for dir %d\n",
				who_am_i(), ndx);
		}
		/* Send all the data we read for this flist to the generator. */
		start_flist_forward(ndx);
		flist = recv_file_list(f_in, ndx);
		flist->parent_ndx = ndx;
		stop_flist_forward();
	}

	iflags = protocol_version >= 29 ? read_shortint(f_in)
		   : ITEM_TRANSFER | ITEM_MISSING_DATA;

	/* Support the protocol-29 keep-alive style. */
	if (protocol_version < 30 && ndx == cur_flist->used && iflags == ITEM_IS_NEW) {
		if (am_sender)
			maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH);
		goto read_loop;
	}

	flist = flist_for_ndx(ndx, "read_ndx_and_attrs");
	if (flist != cur_flist) {
		cur_flist = flist;
		if (am_sender) {
			file_old_total = cur_flist->used;
			for (flist = first_flist; flist != cur_flist; flist = flist->next)
				file_old_total += flist->used;
		}
	}

	if (iflags & ITEM_BASIS_TYPE_FOLLOWS)
		fnamecmp_type = read_byte(f_in);
	*type_ptr = fnamecmp_type;

	if (iflags & ITEM_XNAME_FOLLOWS) {
		if ((len = read_vstring(f_in, buf, MAXPATHLEN)) < 0)
			exit_cleanup(RERR_PROTOCOL);

		if (sanitize_paths) {
			sanitize_path(buf, buf, "", 0, SP_DEFAULT);
			len = strlen(buf);
		}
	} else {
		*buf = '\0';
		len = -1;
	}
	*len_ptr = len;

	if (iflags & ITEM_TRANSFER) {
		int i = ndx - cur_flist->ndx_start;
		if (i < 0 || !S_ISREG(cur_flist->files[i]->mode)) {
			rprintf(FERROR,
				"received request to transfer non-regular file: %d [%s]\n",
				ndx, who_am_i());
			exit_cleanup(RERR_PROTOCOL);
		}
	}

	*iflag_ptr = iflags;
	return ndx;
}

/*
  free a sums struct
  */
void free_sums(struct sum_struct *s)
{
	if (s->sums) {
		free(s->sums);
		free(s->sum2_array);
	}
	free(s);
}

/* This is only called when we aren't preserving permissions.  Figure out what
 * the permissions should be and return them merged back into the mode. */
mode_t dest_mode(mode_t flist_mode, mode_t stat_mode, int dflt_perms,
		 int exists)
{
	int new_mode;
	/* If the file already exists, we'll return the local permissions,
	 * possibly tweaked by the --executability option. */
	if (exists) {
		new_mode = (flist_mode & ~CHMOD_BITS) | (stat_mode & CHMOD_BITS);
		if (preserve_executability && S_ISREG(flist_mode)) {
			/* If the source file is executable, grant execute
			 * rights to everyone who can read, but ONLY if the
			 * file isn't already executable. */
			if (!(flist_mode & 0111))
				new_mode &= ~0111;
			else if (!(stat_mode & 0111))
				new_mode |= (new_mode & 0444) >> 2;
		}
	} else {
		/* Apply destination default permissions and turn
		 * off special permissions. */
		new_mode = flist_mode & (~CHMOD_BITS | dflt_perms);
	}
	return new_mode;
}

static int same_mtime(struct file_struct *file, STRUCT_STAT *st, int extra_accuracy)
{
#ifdef ST_MTIME_NSEC
	uint32 f1_nsec = F_MOD_NSEC_or_0(file);
	uint32 f2_nsec = (uint32)st->ST_MTIME_NSEC;
#else
	uint32 f1_nsec = 0, f2_nsec = 0;
#endif

	if (extra_accuracy) /* ignore modify_window when setting the time after a transfer or checksum check */
		return file->modtime == st->st_mtime && f1_nsec == f2_nsec;

	return same_time(file->modtime, f1_nsec, st->st_mtime , f2_nsec);
}

int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
		   const char *fnamecmp, int flags)
{
	int updated = 0;
	stat_x sx2;
	int change_uid, change_gid;
	mode_t new_mode = file->mode;
	int inherit;

	if (!sxp) {
		if (dry_run)
			return 1;
		if (link_stat(fname, &sx2.st, 0) < 0) {
			rsyserr(FERROR_XFER, errno, "stat %s failed",
				full_fname(fname));
			return 0;
		}
		init_stat_x(&sx2);
		sxp = &sx2;
		inherit = !preserve_perms;
	} else
		inherit = !preserve_perms && file->flags & FLAG_DIR_CREATED;

	if (inherit && S_ISDIR(new_mode) && sxp->st.st_mode & S_ISGID) {
		/* We just created this directory and its setgid
		 * bit is on, so make sure it stays on. */
		new_mode |= S_ISGID;
	}

	if (daemon_chmod_modes && !S_ISLNK(new_mode))
		new_mode = tweak_mode(new_mode, daemon_chmod_modes);

#ifdef SUPPORT_ACLS
	if (preserve_acls && !S_ISLNK(file->mode) && !ACL_READY(*sxp))
		get_acl(fname, sxp);
#endif

	change_uid = am_root && uid_ndx && sxp->st.st_uid != (uid_t)F_OWNER(file);
	change_gid = gid_ndx && !(file->flags & FLAG_SKIP_GROUP)
		  && sxp->st.st_gid != (gid_t)F_GROUP(file);
#ifndef CAN_CHOWN_SYMLINK
	if (S_ISLNK(sxp->st.st_mode)) {
		;
	} else
#endif
	if (change_uid || change_gid) {
		if (DEBUG_GTE(OWN, 1)) {
			if (change_uid) {
				rprintf(FINFO,
					"set uid of %s from %u to %u\n",
					fname, (unsigned)sxp->st.st_uid, F_OWNER(file));
			}
			if (change_gid) {
				rprintf(FINFO,
					"set gid of %s from %u to %u\n",
					fname, (unsigned)sxp->st.st_gid, F_GROUP(file));
			}
		}
		if (am_root >= 0) {
			uid_t uid = change_uid ? (uid_t)F_OWNER(file) : sxp->st.st_uid;
			gid_t gid = change_gid ? (gid_t)F_GROUP(file) : sxp->st.st_gid;
			if (do_lchown(fname, uid, gid) != 0) {
				/* We shouldn't have attempted to change uid
				 * or gid unless have the privilege. */
				rsyserr(FERROR_XFER, errno, "%s %s failed",
					change_uid ? "chown" : "chgrp",
					full_fname(fname));
				goto cleanup;
			}
			if (uid == (uid_t)-1 && sxp->st.st_uid != (uid_t)-1)
				rprintf(FERROR_XFER, "uid 4294967295 (-1) is impossible to set on %s\n", full_fname(fname));
			if (gid == (gid_t)-1 && sxp->st.st_gid != (gid_t)-1)
				rprintf(FERROR_XFER, "gid 4294967295 (-1) is impossible to set on %s\n", full_fname(fname));
			/* A lchown had been done, so we need to re-stat if
			 * the destination had the setuid or setgid bits set
			 * (due to the side effect of the chown call). */
			if (sxp->st.st_mode & (S_ISUID | S_ISGID)) {
				link_stat(fname, &sxp->st,
					  keep_dirlinks && S_ISDIR(sxp->st.st_mode));
			}
		}
		if (change_uid)
			updated |= UPDATED_OWNER;
		if (change_gid)
			updated |= UPDATED_GROUP;
	}

#ifdef SUPPORT_XATTRS
	if (am_root < 0)
		set_stat_xattr(fname, file, new_mode);
	if (preserve_xattrs && fnamecmp)
		set_xattr(fname, file, fnamecmp, sxp);
#endif

	if ((omit_dir_times && S_ISDIR(sxp->st.st_mode))
	 || (omit_link_times && S_ISLNK(sxp->st.st_mode)))
		flags |= ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME | ATTRS_SKIP_CRTIME;
	else {
		if (!preserve_mtimes)
			flags |= ATTRS_SKIP_MTIME;
		if (!atimes_ndx || S_ISDIR(sxp->st.st_mode))
			flags |= ATTRS_SKIP_ATIME;
		/* Don't set the creation date on the root folder of an HFS+ volume. */
		if (sxp->st.st_ino == 2 && S_ISDIR(sxp->st.st_mode))
			flags |= ATTRS_SKIP_CRTIME;
	}
	if (sxp != &sx2)
		memcpy(&sx2.st, &sxp->st, sizeof sx2.st);
	if (!(flags & ATTRS_SKIP_MTIME) && !same_mtime(file, &sxp->st, flags & ATTRS_ACCURATE_TIME)) {
		sx2.st.st_mtime = file->modtime;
#ifdef ST_MTIME_NSEC
		sx2.st.ST_MTIME_NSEC = F_MOD_NSEC_or_0(file);
#endif
		updated |= UPDATED_MTIME;
	}
	if (!(flags & ATTRS_SKIP_ATIME)) {
		time_t file_atime = F_ATIME(file);
		if (flags & ATTRS_ACCURATE_TIME || !same_time(sxp->st.st_atime, 0, file_atime, 0)) {
			sx2.st.st_atime = file_atime;
#ifdef ST_ATIME_NSEC
			sx2.st.ST_ATIME_NSEC = 0;
#endif
			updated |= UPDATED_ATIME;
		}
	}
#ifdef SUPPORT_CRTIMES
	if (crtimes_ndx && !(flags & ATTRS_SKIP_CRTIME)) {
		time_t file_crtime = F_CRTIME(file);
		if (sxp->crtime == 0)
			sxp->crtime = get_create_time(fname, &sxp->st);
		if (!same_time(sxp->crtime, 0L, file_crtime, 0L)) {
			if (
#ifdef HAVE_GETATTRLIST
			     do_setattrlist_crtime(fname, file_crtime) == 0
#elif defined __CYGWIN__
			     do_SetFileTime(fname, file_crtime) == 0
#else
#error Unknown crtimes implementation
#endif
			)
				updated |= UPDATED_CRTIME;
		}
	}
#endif
	if (updated & (UPDATED_MTIME|UPDATED_ATIME)) {
		int ret = set_times(fname, &sx2.st);
		if (ret < 0) {
			rsyserr(FERROR_XFER, errno, "failed to set times on %s", full_fname(fname));
			goto cleanup;
		}
		if (ret > 0) { /* ret == 1 if symlink could not be set */
			updated &= ~(UPDATED_MTIME|UPDATED_ATIME);
			file->flags |= FLAG_TIME_FAILED;
		}
	}

#ifdef SUPPORT_ACLS
	/* It's OK to call set_acl() now, even for a dir, as the generator
	 * will enable owner-writability using chmod, if necessary.
	 *
	 * If set_acl() changes permission bits in the process of setting
	 * an access ACL, it changes sxp->st.st_mode so we know whether we
	 * need to chmod(). */
	if (preserve_acls && !S_ISLNK(new_mode)) {
		if (set_acl(fname, file, sxp, new_mode) > 0)
			updated |= UPDATED_ACLS;
	}
#endif

#ifdef HAVE_CHMOD
	if (!BITS_EQUAL(sxp->st.st_mode, new_mode, CHMOD_BITS)) {
		int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode);
		if (ret < 0) {
			rsyserr(FERROR_XFER, errno,
				"failed to set permissions on %s",
				full_fname(fname));
			goto cleanup;
		}
		if (ret == 0) /* ret == 1 if symlink could not be set */
			updated |= UPDATED_MODE;
	}
#endif

	if (INFO_GTE(NAME, 2) && flags & ATTRS_REPORT) {
		if (updated)
			rprintf(FCLIENT, "%s\n", fname);
		else
			rprintf(FCLIENT, "%s is uptodate\n", fname);
	}
  cleanup:
	if (sxp == &sx2)
		free_stat_x(&sx2);
	return updated;
}

/* This is only called for SIGINT, SIGHUP, and SIGTERM. */
void sig_int(int sig_num)
{
	called_from_signal_handler = 1;

	/* KLUGE: if the user hits Ctrl-C while ssh is prompting
	 * for a password, then our cleanup's sending of a SIGUSR1
	 * signal to all our children may kill ssh before it has a
	 * chance to restore the tty settings (i.e. turn echo back
	 * on).  By sleeping for a short time, ssh gets a bigger
	 * chance to do the right thing.  If child processes are
	 * not ssh waiting for a password, then this tiny delay
	 * shouldn't hurt anything. */
	msleep(400);

	/* If we're an rsync daemon listener (not a daemon server),
	 * we'll exit with status 0 if we received SIGTERM. */
	if (am_daemon && !am_server && sig_num == SIGTERM)
		exit_cleanup(0);

	/* If the signal arrived on the server side (or for the receiver
	 * process on the client), we want to try to do a controlled shutdown
	 * that lets the client side (generator process) know what happened.
	 * To do this, we set a flag and let the normal process handle the
	 * shutdown.  We only attempt this if multiplexed IO is in effect and
	 * we didn't already set the flag. */
	if (!got_kill_signal && (am_server || am_receiver)) {
		got_kill_signal = sig_num;
		called_from_signal_handler = 0;
		return;
	}

	exit_cleanup(RERR_SIGNAL);
}

/* Finish off a file transfer: renaming the file and setting the file's
 * attributes (e.g. permissions, ownership, etc.).  If the robust_rename()
 * call is forced to copy the temp file and partialptr is both non-NULL and
 * not an absolute path, we stage the file into the partial-dir and then
 * rename it into place.  This returns 1 on success or 0 on failure. */
int finish_transfer(const char *fname, const char *fnametmp,
		    const char *fnamecmp, const char *partialptr,
		    struct file_struct *file, int ok_to_set_time,
		    int overwriting_basis)
{
	int ret;
	const char *temp_copy_name = partialptr && *partialptr != '/' ? partialptr : NULL;

	if (inplace) {
		if (DEBUG_GTE(RECV, 1))
			rprintf(FINFO, "finishing %s\n", fname);
		fnametmp = fname;
		goto do_set_file_attrs;
	}

	if (make_backups > 0 && overwriting_basis) {
		int ok = make_backup(fname, False);
		if (!ok)
			exit_cleanup(RERR_FILEIO);
		if (ok == 1 && fnamecmp == fname)
			fnamecmp = get_backup_name(fname);
	}

	/* Change permissions before putting the file into place. */
	set_file_attrs(fnametmp, file, NULL, fnamecmp,
		       ok_to_set_time ? ATTRS_ACCURATE_TIME : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME | ATTRS_SKIP_CRTIME);

	/* move tmp file over real file */
	if (DEBUG_GTE(RECV, 1))
		rprintf(FINFO, "renaming %s to %s\n", fnametmp, fname);
	ret = robust_rename(fnametmp, fname, temp_copy_name, file->mode);
	if (ret < 0) {
		rsyserr(FERROR_XFER, errno, "%s %s -> \"%s\"",
			ret == -2 ? "copy" : "rename",
			full_fname(fnametmp), fname);
		if (!partialptr || (ret == -2 && temp_copy_name)
		 || robust_rename(fnametmp, partialptr, NULL, file->mode) < 0)
			do_unlink(fnametmp);
		return 0;
	}
	if (ret == 0) {
		/* The file was moved into place (not copied), so it's done. */
		return 1;
	}
	/* The file was copied, so tweak the perms of the copied file.  If it
	 * was copied to partialptr, move it into its final destination. */
	fnametmp = temp_copy_name ? temp_copy_name : fname;

  do_set_file_attrs:
	set_file_attrs(fnametmp, file, NULL, fnamecmp,
		       ok_to_set_time ? ATTRS_ACCURATE_TIME : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME | ATTRS_SKIP_CRTIME);

	if (temp_copy_name) {
		if (do_rename(fnametmp, fname) < 0) {
			rsyserr(FERROR_XFER, errno, "rename %s -> \"%s\"",
				full_fname(fnametmp), fname);
			return 0;
		}
		handle_partial_dir(temp_copy_name, PDIR_DELETE);
	}
	return 1;
}

struct file_list *flist_for_ndx(int ndx, const char *fatal_error_loc)
{
	struct file_list *flist = cur_flist;

	if (!flist && !(flist = first_flist))
		goto not_found;

	while (ndx < flist->ndx_start-1) {
		if (flist == first_flist)
			goto not_found;
		flist = flist->prev;
	}
	while (ndx >= flist->ndx_start + flist->used) {
		if (!(flist = flist->next))
			goto not_found;
	}
	return flist;

  not_found:
	if (fatal_error_loc) {
		int first, last;
		if (first_flist) {
			first = first_flist->ndx_start - 1;
			last = first_flist->prev->ndx_start + first_flist->prev->used - 1;
		} else {
			first = 0;
			last = -1;
		}
		rprintf(FERROR,
			"File-list index %d not in %d - %d (%s) [%s]\n",
			ndx, first, last, fatal_error_loc, who_am_i());
		exit_cleanup(RERR_PROTOCOL);
	}
	return NULL;
}

const char *who_am_i(void)
{
	if (am_starting_up)
		return am_server ? "server" : "client";
	return am_sender ? "sender"
	     : am_generator ? "generator"
	     : am_receiver ? "receiver"
	     : "Receiver"; /* pre-forked receiver */
}
/*
 * Routines only used by the sending process.
 *
 * Copyright (C) 1996 Andrew Tridgell
 * Copyright (C) 1996 Paul Mackerras
 * Copyright (C) 2003-2022 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"
#include "inums.h"

extern int do_xfers;
extern int am_server;
extern int am_daemon;
extern int local_server;
extern int inc_recurse;
extern int log_before_transfer;
extern int stdout_format_has_i;
extern int logfile_format_has_i;
extern int want_xattr_optim;
extern int xfer_sum_len;
extern int csum_length;
extern int append_mode;
extern int copy_links;
extern int io_error;
extern int flist_eof;
extern int whole_file;
extern int allowed_lull;
extern int copy_devices;
extern int preserve_xattrs;
extern int protocol_version;
extern int remove_source_files;
extern int updating_basis_file;
extern int make_backups;
extern int inplace;
extern int inplace_partial;
extern int batch_fd;
extern int write_batch;
extern int file_old_total;
extern BOOL want_progress_now;
extern struct stats stats;
extern struct file_list *cur_flist, *first_flist, *dir_flist;
extern char num_dev_ino_buf[4 + 8 + 8];

BOOL extra_flist_sending_enabled;

/**
 * @file
 *
 * The sender gets checksums from the generator, calculates deltas,
 * and transmits them to the receiver.  The sender process runs on the
 * machine holding the source files.
 **/

/**
 * Receive the checksums for a buffer
 **/
static struct sum_struct *receive_sums(int f)
{
	struct sum_struct *s = new(struct sum_struct);
	int lull_mod = protocol_version >= 31 ? 0 : allowed_lull * 5;
	OFF_T offset = 0;
	int32 i;

	read_sum_head(f, s);

	s->sums = NULL;

	if (DEBUG_GTE(DELTASUM, 3)) {
		rprintf(FINFO, "count=%s n=%ld rem=%ld\n",
			big_num(s->count), (long)s->blength, (long)s->remainder);
	}

	if (append_mode > 0) {
		s->flength = (OFF_T)s->count * s->blength;
		if (s->remainder)
			s->flength -= s->blength - s->remainder;
		return s;
	}

	if (s->count == 0)
		return(s);

	s->sums = new_array(struct sum_buf, s->count);
	s->sum2_array = new_array(char, (size_t)s->count * xfer_sum_len);

	for (i = 0; i < s->count; i++) {
		s->sums[i].sum1 = read_int(f);
		read_buf(f, sum2_at(s, i), s->s2length);

		s->sums[i].offset = offset;
		s->sums[i].flags = 0;

		if (i == s->count-1 && s->remainder != 0)
			s->sums[i].len = s->remainder;
		else
			s->sums[i].len = s->blength;
		offset += s->sums[i].len;

		if (lull_mod && !(i % lull_mod))
			maybe_send_keepalive(time(NULL), True);

		if (DEBUG_GTE(DELTASUM, 3)) {
			rprintf(FINFO,
				"chunk[%d] len=%d offset=%s sum1=%08x\n",
				i, s->sums[i].len, big_num(s->sums[i].offset),
				s->sums[i].sum1);
		}
	}

	s->flength = offset;

	return s;
}

void successful_send(int ndx)
{
	char fname[MAXPATHLEN];
	char *failed_op;
	struct file_struct *file;
	struct file_list *flist;
	STRUCT_STAT st;

	if (!remove_source_files)
		return;

	flist = flist_for_ndx(ndx, "successful_send");
	file = flist->files[ndx - flist->ndx_start];
	if (!change_pathname(file, NULL, 0))
		return;
	f_name(file, fname);

	if ((copy_links ? do_stat(fname, &st) : do_lstat(fname, &st)) < 0) {
		failed_op = "re-lstat";
		goto failed;
	}

	if (local_server
	 && (int64)st.st_dev == IVAL64(num_dev_ino_buf, 4)
	 && (int64)st.st_ino == IVAL64(num_dev_ino_buf, 4 + 8)) {
		rprintf(FERROR_XFER, "ERROR: Skipping sender remove of destination file: %s\n", fname);
		return;
	}

	if (st.st_size != F_LENGTH(file) || st.st_mtime != file->modtime
#ifdef ST_MTIME_NSEC
	 || (NSEC_BUMP(file) && (uint32)st.ST_MTIME_NSEC != F_MOD_NSEC(file))
#endif
	) {
		rprintf(FERROR_XFER, "ERROR: Skipping sender remove for changed file: %s\n", fname);
		return;
	}

	if (do_unlink(fname) < 0) {
		failed_op = "remove";
	  failed:
		if (errno == ENOENT)
			rprintf(FINFO, "sender file already removed: %s\n", fname);
		else
			rsyserr(FERROR_XFER, errno, "sender failed to %s %s", failed_op, fname);
	} else {
		if (INFO_GTE(REMOVE, 1))
			rprintf(FINFO, "sender removed %s\n", fname);
	}
}

static void write_ndx_and_attrs(int f_out, int ndx, int iflags,
				const char *fname, struct file_struct *file,
				uchar fnamecmp_type, char *buf, int len)
{
	write_ndx(f_out, ndx);
	if (protocol_version < 29)
		return;
	write_shortint(f_out, iflags);
	if (iflags & ITEM_BASIS_TYPE_FOLLOWS)
		write_byte(f_out, fnamecmp_type);
	if (iflags & ITEM_XNAME_FOLLOWS)
		write_vstring(f_out, buf, len);
#ifdef SUPPORT_XATTRS
	if (preserve_xattrs && iflags & ITEM_REPORT_XATTR && do_xfers
	 && !(want_xattr_optim && BITS_SET(iflags, ITEM_XNAME_FOLLOWS|ITEM_LOCAL_CHANGE)))
		send_xattr_request(fname, file, f_out);
#endif
}

void send_files(int f_in, int f_out)
{
	int fd = -1;
	struct sum_struct *s;
	struct map_struct *mbuf = NULL;
	STRUCT_STAT st;
	char fname[MAXPATHLEN], xname[MAXPATHLEN];
	const char *path, *slash;
	uchar fnamecmp_type;
	int iflags, xlen;
	struct file_struct *file;
	int phase = 0, max_phase = protocol_version >= 29 ? 2 : 1;
	int itemizing = am_server ? logfile_format_has_i : stdout_format_has_i;
	enum logcode log_code = log_before_transfer ? FLOG : FINFO;
	int f_xfer = write_batch < 0 ? batch_fd : f_out;
	int save_io_error = io_error;
	int ndx, j;

	if (DEBUG_GTE(SEND, 1))
		rprintf(FINFO, "send_files starting\n");

	if (whole_file < 0)
		whole_file = 0;

	progress_init();

	while (1) {
		if (inc_recurse) {
			send_extra_file_list(f_out, MIN_FILECNT_LOOKAHEAD);
			extra_flist_sending_enabled = !flist_eof;
		}

		/* This call also sets cur_flist. */
		ndx = read_ndx_and_attrs(f_in, f_out, &iflags, &fnamecmp_type,
					 xname, &xlen);
		extra_flist_sending_enabled = False;

		if (ndx == NDX_DONE) {
			if (!am_server && cur_flist) {
				set_current_file_index(NULL, 0);
				if (INFO_GTE(PROGRESS, 2))
					end_progress(0);
			}
			if (inc_recurse && first_flist) {
				file_old_total -= first_flist->used;
				flist_free(first_flist);
				if (first_flist) {
					if (first_flist == cur_flist)
						file_old_total = cur_flist->used;
					write_ndx(f_out, NDX_DONE);
					continue;
				}
			}
			if (++phase > max_phase)
				break;
			if (DEBUG_GTE(SEND, 1))
				rprintf(FINFO, "send_files phase=%d\n", phase);
			write_ndx(f_out, NDX_DONE);
			continue;
		}

		if (inc_recurse)
			send_extra_file_list(f_out, MIN_FILECNT_LOOKAHEAD);

		if (ndx - cur_flist->ndx_start >= 0)
			file = cur_flist->files[ndx - cur_flist->ndx_start];
		else if (cur_flist->parent_ndx < 0)
			exit_cleanup(RERR_PROTOCOL);
		else
			file = dir_flist->files[cur_flist->parent_ndx];
		if (F_PATHNAME(file)) {
			path = F_PATHNAME(file);
			slash = "/";
		} else {
			path = slash = "";
		}
		if (!change_pathname(file, NULL, 0))
			continue;
		f_name(file, fname);

		if (DEBUG_GTE(SEND, 1))
			rprintf(FINFO, "send_files(%d, %s%s%s)\n", ndx, path,slash,fname);

#ifdef SUPPORT_XATTRS
		if (preserve_xattrs && iflags & ITEM_REPORT_XATTR && do_xfers
		 && !(want_xattr_optim && BITS_SET(iflags, ITEM_XNAME_FOLLOWS|ITEM_LOCAL_CHANGE)))
			recv_xattr_request(file, f_in);
#endif

		if (!(iflags & ITEM_TRANSFER)) {
			maybe_log_item(file, iflags, itemizing, xname);
			write_ndx_and_attrs(f_out, ndx, iflags, fname, file, fnamecmp_type, xname, xlen);
			if (iflags & ITEM_IS_NEW) {
				stats.created_files++;
				if (S_ISREG(file->mode)) {
					/* Nothing further to count. */
				} else if (S_ISDIR(file->mode))
					stats.created_dirs++;
#ifdef SUPPORT_LINKS
				else if (S_ISLNK(file->mode))
					stats.created_symlinks++;
#endif
				else if (IS_DEVICE(file->mode))
					stats.created_devices++;
				else
					stats.created_specials++;
			}
			continue;
		}
		if (phase == 2) {
			rprintf(FERROR,
				"got transfer request in phase 2 [%s]\n",
				who_am_i());
			exit_cleanup(RERR_PROTOCOL);
		}

		if (file->flags & FLAG_FILE_SENT) {
			if (csum_length == SHORT_SUM_LENGTH) {
				/* For inplace: redo phase turns off the backup
				 * flag so that we do a regular inplace send. */
				make_backups = -make_backups;
				append_mode = -append_mode;
				csum_length = SUM_LENGTH;
			}
		} else {
			if (csum_length != SHORT_SUM_LENGTH) {
				make_backups = -make_backups;
				append_mode = -append_mode;
				csum_length = SHORT_SUM_LENGTH;
			}
			if (iflags & ITEM_IS_NEW)
				stats.created_files++;
		}

		updating_basis_file = (inplace_partial && fnamecmp_type == FNAMECMP_PARTIAL_DIR)
		    || (inplace && (protocol_version >= 29 ? fnamecmp_type == FNAMECMP_FNAME : make_backups <= 0));

		if (!am_server)
			set_current_file_index(file, ndx);
		stats.xferred_files++;
		stats.total_transferred_size += F_LENGTH(file);

		remember_initial_stats();

		if (!do_xfers) { /* log the transfer */
			log_item(FCLIENT, file, iflags, NULL);
			write_ndx_and_attrs(f_out, ndx, iflags, fname, file, fnamecmp_type, xname, xlen);
			continue;
		}

		if (!(s = receive_sums(f_in))) {
			io_error |= IOERR_GENERAL;
			rprintf(FERROR_XFER, "receive_sums failed\n");
			exit_cleanup(RERR_PROTOCOL);
		}

		fd = do_open_checklinks(fname);
		if (fd == -1) {
			if (errno == ENOENT) {
				enum logcode c = am_daemon && protocol_version < 28 ? FERROR : FWARNING;
				io_error |= IOERR_VANISHED;
				rprintf(c, "file has vanished: %s\n",
					full_fname(fname));
			} else {
				io_error |= IOERR_GENERAL;
				rsyserr(FERROR_XFER, errno,
					"send_files failed to open %s",
					full_fname(fname));
			}
			free_sums(s);
			if (protocol_version >= 30)
				send_msg_int(MSG_NO_SEND, ndx);
			continue;
		}

		/* map the local file */
		if (do_fstat(fd, &st) != 0) {
			io_error |= IOERR_GENERAL;
			rsyserr(FERROR_XFER, errno, "fstat failed");
			free_sums(s);
			close(fd);
			exit_cleanup(RERR_FILEIO);
		}

		if (IS_DEVICE(st.st_mode)) {
			if (!copy_devices) {
				rprintf(FERROR, "attempt to copy device contents without --copy-devices\n");
				exit_cleanup(RERR_PROTOCOL);
			}
			if (st.st_size == 0)
				st.st_size = get_device_size(fd, fname);
		}

		if (append_mode > 0 && st.st_size < F_LENGTH(file)) {
			rprintf(FWARNING, "skipped diminished file: %s\n",
				full_fname(fname));
			free_sums(s);
			close(fd);
			if (protocol_version >= 30)
				send_msg_int(MSG_NO_SEND, ndx);
			continue;
		}

		if (st.st_size) {
			int32 read_size = MAX(s->blength * 3, MAX_MAP_SIZE);
			mbuf = map_file(fd, st.st_size, read_size, s->blength);
		} else
			mbuf = NULL;

		if (DEBUG_GTE(DELTASUM, 2)) {
			rprintf(FINFO, "send_files mapped %s%s%s of size %s\n",
				path,slash,fname, big_num(st.st_size));
		}

		write_ndx_and_attrs(f_out, ndx, iflags, fname, file, fnamecmp_type, xname, xlen);
		write_sum_head(f_xfer, s);

		if (DEBUG_GTE(DELTASUM, 2))
			rprintf(FINFO, "calling match_sums %s%s%s\n", path,slash,fname);

		if (log_before_transfer)
			log_item(FCLIENT, file, iflags, NULL);
		else if (!am_server && INFO_GTE(NAME, 1) && INFO_EQ(PROGRESS, 1))
			rprintf(FCLIENT, "%s\n", fname);

		set_compression(fname);

		match_sums(f_xfer, s, mbuf, st.st_size);
		if (INFO_GTE(PROGRESS, 1))
			end_progress(st.st_size);
		else if (want_progress_now)
			instant_progress(fname);

		log_item(log_code, file, iflags, NULL);

		if (mbuf) {
			j = unmap_file(mbuf);
			if (j) {
				io_error |= IOERR_GENERAL;
				rsyserr(FERROR_XFER, j,
					"read errors mapping %s",
					full_fname(fname));
			}
		}
		close(fd);

		free_sums(s);

		if (DEBUG_GTE(SEND, 1))
			rprintf(FINFO, "sender finished %s%s%s\n", path,slash,fname);

		/* Flag that we actually sent this entry. */
		file->flags |= FLAG_FILE_SENT;
	}
	if (make_backups < 0)
		make_backups = -make_backups;

	if (io_error != save_io_error && protocol_version >= 30)
		send_msg_int(MSG_IO_ERROR, io_error);

	if (DEBUG_GTE(SEND, 1))
		rprintf(FINFO, "send files finished\n");

	match_report();

	write_ndx(f_out, NDX_DONE);
}
/*
 * Socket functions used in rsync.
 *
 * Copyright (C) 1992-2001 Andrew Tridgell <tridge@samba.org>
 * Copyright (C) 2001, 2002 Martin Pool <mbp@samba.org>
 * Copyright (C) 2003-2020 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

/* This file is now converted to use the new-style getaddrinfo()
 * interface, which supports IPv6 but is also supported on recent
 * IPv4-only machines.  On systems that don't have that interface, we
 * emulate it using the KAME implementation. */

#include "rsync.h"
#include "itypes.h"
#include "ifuncs.h"
#ifdef HAVE_NETINET_IN_SYSTM_H
#include <netinet/in_systm.h>
#endif
#ifdef HAVE_NETINET_IP_H
#include <netinet/ip.h>
#endif
#include <netinet/tcp.h>

extern char *bind_address;
extern char *sockopts;
extern int default_af_hint;
extern int connect_timeout;
extern int pid_file_fd;

#ifdef HAVE_SIGACTION
static struct sigaction sigact;
#endif

static int sock_exec(const char *prog);

/* Establish a proxy connection on an open socket to a web proxy by using the
 * CONNECT method.  If proxy_user and proxy_pass are not NULL, they are used to
 * authenticate to the proxy using the "Basic" proxy-authorization protocol. */
static int establish_proxy_connection(int fd, char *host, int port, char *proxy_user, char *proxy_pass)
{
	char *cp, buffer[1024];
	char *authhdr, authbuf[1024];
	int len;

	if (proxy_user && proxy_pass) {
		stringjoin(buffer, sizeof buffer,
			 proxy_user, ":", proxy_pass, NULL);
		len = strlen(buffer);

		if ((len*8 + 5) / 6 >= (int)sizeof authbuf - 3) {
			rprintf(FERROR,
				"authentication information is too long\n");
			return -1;
		}

		base64_encode(buffer, len, authbuf, 1);
		authhdr = "\r\nProxy-Authorization: Basic ";
	} else {
		*authbuf = '\0';
		authhdr = "";
	}

	len = snprintf(buffer, sizeof buffer, "CONNECT %s:%d HTTP/1.0%s%s\r\n\r\n", host, port, authhdr, authbuf);
	assert(len > 0 && len < (int)sizeof buffer);
	if (write(fd, buffer, len) != len) {
		rsyserr(FERROR, errno, "failed to write to proxy");
		return -1;
	}

	for (cp = buffer; cp < &buffer[sizeof buffer - 1]; cp++) {
		if (read(fd, cp, 1) != 1) {
			rsyserr(FERROR, errno, "failed to read from proxy");
			return -1;
		}
		if (*cp == '\n')
			break;
	}

	if (*cp != '\n')
		cp++;
	*cp-- = '\0';
	if (*cp == '\r')
		*cp = '\0';
	if (strncmp(buffer, "HTTP/", 5) != 0) {
		rprintf(FERROR, "bad response from proxy -- %s\n",
			buffer);
		return -1;
	}
	for (cp = &buffer[5]; isDigit(cp) || *cp == '.'; cp++) {}
	while (*cp == ' ')
		cp++;
	if (*cp != '2') {
		rprintf(FERROR, "bad response from proxy -- %s\n",
			buffer);
		return -1;
	}
	/* throw away the rest of the HTTP header */
	while (1) {
		for (cp = buffer; cp < &buffer[sizeof buffer - 1]; cp++) {
			if (read(fd, cp, 1) != 1) {
				rsyserr(FERROR, errno,
					"failed to read from proxy");
				return -1;
			}
			if (*cp == '\n')
				break;
		}
		if (cp > buffer && *cp == '\n')
			cp--;
		if (cp == buffer && (*cp == '\n' || *cp == '\r'))
			break;
	}
	return 0;
}


/* Try to set the local address for a newly-created socket.
 * Return -1 if this fails. */
int try_bind_local(int s, int ai_family, int ai_socktype,
		   const char *bind_addr)
{
	int error;
	struct addrinfo bhints, *bres_all, *r;

	memset(&bhints, 0, sizeof bhints);
	bhints.ai_family = ai_family;
	bhints.ai_socktype = ai_socktype;
	bhints.ai_flags = AI_PASSIVE;
	if ((error = getaddrinfo(bind_addr, NULL, &bhints, &bres_all))) {
		rprintf(FERROR, RSYNC_NAME ": getaddrinfo %s: %s\n",
			bind_addr, gai_strerror(error));
		return -1;
	}

	for (r = bres_all; r; r = r->ai_next) {
		if (bind(s, r->ai_addr, r->ai_addrlen) == -1)
			continue;
		freeaddrinfo(bres_all);
		return s;
	}

	/* no error message; there might be some problem that allows
	 * creation of the socket but not binding, perhaps if the
	 * machine has no ipv6 address of this name. */
	freeaddrinfo(bres_all);
	return -1;
}

/* connect() timeout handler based on alarm() */
static void contimeout_handler(UNUSED(int val))
{
	connect_timeout = -1;
}

/* Open a socket to a tcp remote host with the specified port.
 *
 * Based on code from Warren.  Proxy support by Stephen Rothwell.
 * getaddrinfo() rewrite contributed by KAME.net.
 *
 * Now that we support IPv6 we need to look up the remote machine's address
 * first, using af_hint to set a preference for the type of address.  Then
 * depending on whether it has v4 or v6 addresses we try to open a connection.
 *
 * The loop allows for machines with some addresses which may not be reachable,
 * perhaps because we can't e.g. route ipv6 to that network but we can get ip4
 * packets through.
 *
 * bind_addr: local address to use.  Normally NULL to bind the wildcard address.
 *
 * af_hint: address family, e.g. AF_INET or AF_INET6. */
int open_socket_out(char *host, int port, const char *bind_addr, int af_hint)
{
	int type = SOCK_STREAM;
	int error, s, j, addr_cnt, *errnos;
	struct addrinfo hints, *res0, *res;
	char portbuf[10];
	char *h, *cp;
	int proxied = 0;
	char buffer[1024];
	char *proxy_user = NULL, *proxy_pass = NULL;

	/* if we have a RSYNC_PROXY env variable then redirect our
	 * connection via a web proxy at the given address. */
	h = getenv("RSYNC_PROXY");
	proxied = h != NULL && *h != '\0';

	if (proxied) {
		strlcpy(buffer, h, sizeof buffer);

		/* Is the USER:PASS@ prefix present? */
		if ((cp = strrchr(buffer, '@')) != NULL) {
			*cp++ = '\0';
			/* The remainder is the HOST:PORT part. */
			h = cp;

			if ((cp = strchr(buffer, ':')) == NULL) {
				rprintf(FERROR,
					"invalid proxy specification: should be USER:PASS@HOST:PORT\n");
				return -1;
			}
			*cp++ = '\0';

			proxy_user = buffer;
			proxy_pass = cp;
		} else {
			/* The whole buffer is the HOST:PORT part. */
			h = buffer;
		}

		if ((cp = strchr(h, ':')) == NULL) {
			rprintf(FERROR,
				"invalid proxy specification: should be HOST:PORT\n");
			return -1;
		}
		*cp++ = '\0';
		strlcpy(portbuf, cp, sizeof portbuf);
		if (DEBUG_GTE(CONNECT, 1)) {
			rprintf(FINFO, "connection via http proxy %s port %s\n",
				h, portbuf);
		}
	} else {
		snprintf(portbuf, sizeof portbuf, "%d", port);
		h = host;
	}

	memset(&hints, 0, sizeof hints);
	hints.ai_family = af_hint;
	hints.ai_socktype = type;
	error = getaddrinfo(h, portbuf, &hints, &res0);
	if (error) {
		rprintf(FERROR, RSYNC_NAME ": getaddrinfo: %s %s: %s\n",
			h, portbuf, gai_strerror(error));
		return -1;
	}

	for (res = res0, addr_cnt = 0; res; res = res->ai_next, addr_cnt++) {}
	errnos = new_array0(int, addr_cnt);

	s = -1;
	/* Try to connect to all addresses for this machine until we get
	 * through.  It might e.g. be multi-homed, or have both IPv4 and IPv6
	 * addresses.  We need to create a socket for each record, since the
	 * address record tells us what protocol to use to try to connect. */
	for (res = res0, j = 0; res; res = res->ai_next, j++) {
		s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
		if (s < 0)
			continue;

		if (bind_addr
		 && try_bind_local(s, res->ai_family, type,
				   bind_addr) == -1) {
			close(s);
			s = -1;
			continue;
		}
		if (connect_timeout > 0) {
			SIGACTION(SIGALRM, contimeout_handler);
			alarm(connect_timeout);
		}

		set_socket_options(s, sockopts);
		while (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
			if (connect_timeout < 0)
				exit_cleanup(RERR_CONTIMEOUT);
			if (errno == EINTR)
				continue;
			close(s);
			s = -1;
			break;
		}

		if (connect_timeout > 0)
			alarm(0);

		if (s < 0) {
			errnos[j] = errno;
			continue;
		}

		if (proxied && establish_proxy_connection(s, host, port, proxy_user, proxy_pass) != 0) {
			close(s);
			s = -1;
			continue;
		}
		if (DEBUG_GTE(CONNECT, 2)) {
			char buf[2048];
			if ((error = getnameinfo(res->ai_addr, res->ai_addrlen, buf, sizeof buf, NULL, 0, NI_NUMERICHOST)) != 0)
				snprintf(buf, sizeof buf, "*getnameinfo failure: %s*", gai_strerror(error));
			rprintf(FINFO, "Connected to %s (%s)\n", h, buf);
		}
		break;
	}

	if (s < 0 || DEBUG_GTE(CONNECT, 2)) {
		char buf[2048];
		for (res = res0, j = 0; res; res = res->ai_next, j++) {
			if (errnos[j] == 0)
				continue;
			if ((error = getnameinfo(res->ai_addr, res->ai_addrlen, buf, sizeof buf, NULL, 0, NI_NUMERICHOST)) != 0)
				snprintf(buf, sizeof buf, "*getnameinfo failure: %s*", gai_strerror(error));
			rsyserr(FERROR, errnos[j], "failed to connect to %s (%s)", h, buf);
		}
		if (s < 0)
			s = -1;
	}

	freeaddrinfo(res0);
	free(errnos);

	return s;
}


/* Open an outgoing socket, but allow for it to be intercepted by
 * $RSYNC_CONNECT_PROG, which will execute a program across a TCP
 * socketpair rather than really opening a socket.
 *
 * We use this primarily in testing to detect TCP flow bugs, but not
 * cause security problems by really opening remote connections.
 *
 * This is based on the Samba LIBSMB_PROG feature.
 *
 * bind_addr: local address to use.  Normally NULL to get the stack default. */
int open_socket_out_wrapped(char *host, int port, const char *bind_addr, int af_hint)
{
	char *prog = getenv("RSYNC_CONNECT_PROG");

	if (prog && strchr(prog, '%')) {
		int hlen = strlen(host);
		int len = strlen(prog) + 1;
		char *f, *t;
		for (f = prog; *f; f++) {
			if (*f != '%')
				continue;
			/* Compute more than enough room. */
			if (f[1] == '%')
				f++;
			else
				len += hlen;
		}
		f = prog;
		prog = new_array(char, len);
		for (t = prog; *f; f++) {
			if (*f == '%') {
				switch (*++f) {
				case '%':
					/* Just skips the extra '%'. */
					break;
				case 'H':
					memcpy(t, host, hlen);
					t += hlen;
					continue;
				default:
					f--; /* pass % through */
					break;
				}
			}
			*t++ = *f;
		}
		*t = '\0';
	}

	if (DEBUG_GTE(CONNECT, 1)) {
		rprintf(FINFO, "%sopening tcp connection to %s port %d\n",
			prog ? "Using RSYNC_CONNECT_PROG instead of " : "",
			host, port);
	}
	if (prog)
		return sock_exec(prog);
	return open_socket_out(host, port, bind_addr, af_hint);
}


/* Open one or more sockets for incoming data using the specified type,
 * port, and address.
 *
 * The getaddrinfo() call may return several address results, e.g. for
 * the machine's IPv4 and IPv6 name.
 *
 * We return an array of file-descriptors to the sockets, with a trailing
 * -1 value to indicate the end of the list.
 *
 * bind_addr: local address to bind, or NULL to allow it to default. */
static int *open_socket_in(int type, int port, const char *bind_addr,
			   int af_hint)
{
	int one = 1;
	int s, *socks, maxs, i, ecnt;
	struct addrinfo hints, *all_ai, *resp;
	char portbuf[10], **errmsgs;
	int error;

	memset(&hints, 0, sizeof hints);
	hints.ai_family = af_hint;
	hints.ai_socktype = type;
	hints.ai_flags = AI_PASSIVE;
	snprintf(portbuf, sizeof portbuf, "%d", port);
	error = getaddrinfo(bind_addr, portbuf, &hints, &all_ai);
	if (error) {
		rprintf(FERROR, RSYNC_NAME ": getaddrinfo: bind address %s: %s\n",
			bind_addr, gai_strerror(error));
		return NULL;
	}

	/* Count max number of sockets we might open. */
	for (maxs = 0, resp = all_ai; resp; resp = resp->ai_next, maxs++) {}

	socks = new_array(int, maxs + 1);
	errmsgs = new_array(char *, maxs);

	/* We may not be able to create the socket, if for example the
	 * machine knows about IPv6 in the C library, but not in the
	 * kernel. */
	for (resp = all_ai, i = ecnt = 0; resp; resp = resp->ai_next) {
		s = socket(resp->ai_family, resp->ai_socktype,
			   resp->ai_protocol);

		if (s == -1) {
			int r = asprintf(&errmsgs[ecnt++],
				"socket(%d,%d,%d) failed: %s\n",
				(int)resp->ai_family, (int)resp->ai_socktype,
				(int)resp->ai_protocol, strerror(errno));
			if (r < 0)
				out_of_memory("open_socket_in");
			/* See if there's another address that will work... */
			continue;
		}

		setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
			   (char *)&one, sizeof one);
		if (sockopts)
			set_socket_options(s, sockopts);
		else
			set_socket_options(s, lp_socket_options());

#ifdef IPV6_V6ONLY
		if (resp->ai_family == AF_INET6) {
			if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&one, sizeof one) < 0
			 && default_af_hint != AF_INET6) {
				close(s);
				continue;
			}
		}
#endif

		/* Now we've got a socket - we need to bind it. */
		if (bind(s, resp->ai_addr, resp->ai_addrlen) < 0) {
			/* Nope, try another */
			int r = asprintf(&errmsgs[ecnt++],
				"bind() failed: %s (address-family %d)\n",
				strerror(errno), (int)resp->ai_family);
			if (r < 0)
				out_of_memory("open_socket_in");
			close(s);
			continue;
		}

		socks[i++] = s;
	}
	socks[i] = -1;

	if (all_ai)
		freeaddrinfo(all_ai);

	/* Only output the socket()/bind() messages if we were totally
	 * unsuccessful, or if the daemon is being run with -vv. */
	for (s = 0; s < ecnt; s++) {
		if (!i || DEBUG_GTE(BIND, 1))
			rwrite(FLOG, errmsgs[s], strlen(errmsgs[s]), 0);
		free(errmsgs[s]);
	}
	free(errmsgs);

	if (!i) {
		rprintf(FERROR,
			"unable to bind any inbound sockets on port %d\n",
			port);
		free(socks);
		return NULL;
	}
	return socks;
}


/* Determine if a file descriptor is in fact a socket. */
int is_a_socket(int fd)
{
	int v;
	socklen_t l = sizeof (int);

	/* Parameters to getsockopt, setsockopt etc are very
	 * unstandardized across platforms, so don't be surprised if
	 * there are compiler warnings on e.g. SCO OpenSwerver or AIX.
	 * It seems they all eventually get the right idea.
	 *
	 * Debian says: ``The fifth argument of getsockopt and
	 * setsockopt is in reality an int [*] (and this is what BSD
	 * 4.* and libc4 and libc5 have).  Some POSIX confusion
	 * resulted in the present socklen_t.  The draft standard has
	 * not been adopted yet, but glibc2 already follows it and
	 * also has socklen_t [*]. See also accept(2).''
	 *
	 * We now return to your regularly scheduled programming.  */
	return getsockopt(fd, SOL_SOCKET, SO_TYPE, (char *)&v, &l) == 0;
}


static void sigchld_handler(UNUSED(int val))
{
#ifdef WNOHANG
	while (waitpid(-1, NULL, WNOHANG) > 0) {}
#endif
#ifndef HAVE_SIGACTION
	signal(SIGCHLD, sigchld_handler);
#endif
}


void start_accept_loop(int port, int (*fn)(int, int))
{
	fd_set deffds;
	int *sp, maxfd, i;

#ifdef HAVE_SIGACTION
	sigact.sa_flags = SA_NOCLDSTOP;
#endif

	/* open an incoming socket */
	sp = open_socket_in(SOCK_STREAM, port, bind_address, default_af_hint);
	if (sp == NULL)
		exit_cleanup(RERR_SOCKETIO);

	/* ready to listen */
	FD_ZERO(&deffds);
	for (i = 0, maxfd = -1; sp[i] >= 0; i++) {
		if (listen(sp[i], lp_listen_backlog()) < 0) {
			rsyserr(FERROR, errno, "listen() on socket failed");
#ifdef INET6
			if (errno == EADDRINUSE && i > 0) {
				rprintf(FINFO, "Try using --ipv4 or --ipv6 to avoid this listen() error.\n");
			}
#endif
			exit_cleanup(RERR_SOCKETIO);
		}
		FD_SET(sp[i], &deffds);
		if (maxfd < sp[i])
			maxfd = sp[i];
	}

	/* now accept incoming connections - forking a new process
	 * for each incoming connection */
	while (1) {
		fd_set fds;
		pid_t pid;
		int fd;
		struct sockaddr_storage addr;
		socklen_t addrlen = sizeof addr;

		/* close log file before the potentially very long select so
		 * file can be trimmed by another process instead of growing
		 * forever */
		logfile_close();

#ifdef FD_COPY
		FD_COPY(&deffds, &fds);
#else
		fds = deffds;
#endif

		if (select(maxfd + 1, &fds, NULL, NULL, NULL) < 1)
			continue;

		for (i = 0, fd = -1; sp[i] >= 0; i++) {
			if (FD_ISSET(sp[i], &fds)) {
				fd = accept(sp[i], (struct sockaddr *)&addr, &addrlen);
				break;
			}
		}

		if (fd < 0)
			continue;

		SIGACTION(SIGCHLD, sigchld_handler);

		if ((pid = fork()) == 0) {
			int ret;
			if (pid_file_fd >= 0)
				close(pid_file_fd);
			for (i = 0; sp[i] >= 0; i++)
				close(sp[i]);
			/* Re-open log file in child before possibly giving
			 * up privileges (see logfile_close() above). */
			logfile_reopen();
			ret = fn(fd, fd);
			close_all();
			_exit(ret);
		} else if (pid < 0) {
			rsyserr(FERROR, errno,
				"could not create child server process");
			close(fd);
			/* This might have happened because we're
			 * overloaded.  Sleep briefly before trying to
			 * accept again. */
			sleep(2);
		} else {
			/* Parent doesn't need this fd anymore. */
			close(fd);
		}
	}
}


enum SOCK_OPT_TYPES {OPT_BOOL,OPT_INT,OPT_ON};

struct
{
  char *name;
  int level;
  int option;
  int value;
  int opttype;
} socket_options[] = {
  {"SO_KEEPALIVE",      SOL_SOCKET,    SO_KEEPALIVE,    0,                 OPT_BOOL},
  {"SO_REUSEADDR",      SOL_SOCKET,    SO_REUSEADDR,    0,                 OPT_BOOL},
#ifdef SO_BROADCAST
  {"SO_BROADCAST",      SOL_SOCKET,    SO_BROADCAST,    0,                 OPT_BOOL},
#endif
#ifdef TCP_NODELAY
  {"TCP_NODELAY",       IPPROTO_TCP,   TCP_NODELAY,     0,                 OPT_BOOL},
#endif
#ifdef IPTOS_LOWDELAY
  {"IPTOS_LOWDELAY",    IPPROTO_IP,    IP_TOS,          IPTOS_LOWDELAY,    OPT_ON},
#endif
#ifdef IPTOS_THROUGHPUT
  {"IPTOS_THROUGHPUT",  IPPROTO_IP,    IP_TOS,          IPTOS_THROUGHPUT,  OPT_ON},
#endif
#ifdef SO_SNDBUF
  {"SO_SNDBUF",         SOL_SOCKET,    SO_SNDBUF,       0,                 OPT_INT},
#endif
#ifdef SO_RCVBUF
  {"SO_RCVBUF",         SOL_SOCKET,    SO_RCVBUF,       0,                 OPT_INT},
#endif
#ifdef SO_SNDLOWAT
  {"SO_SNDLOWAT",       SOL_SOCKET,    SO_SNDLOWAT,     0,                 OPT_INT},
#endif
#ifdef SO_RCVLOWAT
  {"SO_RCVLOWAT",       SOL_SOCKET,    SO_RCVLOWAT,     0,                 OPT_INT},
#endif
#ifdef SO_SNDTIMEO
  {"SO_SNDTIMEO",       SOL_SOCKET,    SO_SNDTIMEO,     0,                 OPT_INT},
#endif
#ifdef SO_RCVTIMEO
  {"SO_RCVTIMEO",       SOL_SOCKET,    SO_RCVTIMEO,     0,                 OPT_INT},
#endif
  {NULL,0,0,0,0}
};


/* Set user socket options. */
void set_socket_options(int fd, char *options)
{
	char *tok;

	if (!options || !*options)
		return;

	options = strdup(options);

	for (tok = strtok(options, " \t,"); tok; tok = strtok(NULL," \t,")) {
		int ret=0,i;
		int value = 1;
		char *p;
		int got_value = 0;

		if ((p = strchr(tok,'='))) {
			*p = 0;
			value = atoi(p+1);
			got_value = 1;
		}

		for (i = 0; socket_options[i].name; i++) {
			if (strcmp(socket_options[i].name,tok)==0)
				break;
		}

		if (!socket_options[i].name) {
			rprintf(FERROR,"Unknown socket option %s\n",tok);
			continue;
		}

		switch (socket_options[i].opttype) {
		case OPT_BOOL:
		case OPT_INT:
			ret = setsockopt(fd,socket_options[i].level,
					 socket_options[i].option,
					 (char *)&value, sizeof (int));
			break;

		case OPT_ON:
			if (got_value)
				rprintf(FERROR,"syntax error -- %s does not take a value\n",tok);

			{
				int on = socket_options[i].value;
				ret = setsockopt(fd,socket_options[i].level,
						 socket_options[i].option,
						 (char *)&on, sizeof (int));
			}
			break;
		}

		if (ret != 0) {
			rsyserr(FERROR, errno,
				"failed to set socket option %s", tok);
		}
	}

	free(options);
}


/* This is like socketpair but uses tcp.  The function guarantees that nobody
 * else can attach to the socket, or if they do that this function fails and
 * the socket gets closed.  Returns 0 on success, -1 on failure.  The resulting
 * file descriptors are symmetrical.  Currently only for RSYNC_CONNECT_PROG. */
static int socketpair_tcp(int fd[2])
{
	int listener;
	struct sockaddr_in sock;
	struct sockaddr_in sock2;
	socklen_t socklen = sizeof sock;
	int connect_done = 0;

	fd[0] = fd[1] = listener = -1;

	memset(&sock, 0, sizeof sock);

	if ((listener = socket(PF_INET, SOCK_STREAM, 0)) == -1)
		goto failed;

	memset(&sock2, 0, sizeof sock2);
#ifdef HAVE_SOCKADDR_IN_LEN
	sock2.sin_len = sizeof sock2;
#endif
	sock2.sin_family = PF_INET;
	sock2.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

	if (bind(listener, (struct sockaddr *)&sock2, sizeof sock2) != 0
	 || listen(listener, 1) != 0
	 || getsockname(listener, (struct sockaddr *)&sock, &socklen) != 0
	 || (fd[1] = socket(PF_INET, SOCK_STREAM, 0)) == -1)
		goto failed;

	set_nonblocking(fd[1]);

	sock.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

	if (connect(fd[1], (struct sockaddr *)&sock, sizeof sock) == -1) {
		if (errno != EINPROGRESS)
			goto failed;
	} else
		connect_done = 1;

	if ((fd[0] = accept(listener, (struct sockaddr *)&sock2, &socklen)) == -1)
		goto failed;

	close(listener);
	listener = -1;

	set_blocking(fd[1]);

	if (connect_done == 0) {
		if (connect(fd[1], (struct sockaddr *)&sock, sizeof sock) != 0 && errno != EISCONN)
			goto failed;
	}

	/* all OK! */
	return 0;

 failed:
	if (fd[0] != -1)
		close(fd[0]);
	if (fd[1] != -1)
		close(fd[1]);
	if (listener != -1)
		close(listener);
	return -1;
}


/* Run a program on a local tcp socket, so that we can talk to it's stdin and
 * stdout.  This is used to fake a connection to a daemon for testing -- not
 * for the normal case of running SSH.
 *
 * Returns a socket which is attached to a subprocess running "prog". stdin and
 * stdout are attached. stderr is left attached to the original stderr. */
static int sock_exec(const char *prog)
{
	pid_t pid;
	int fd[2];

	if (socketpair_tcp(fd) != 0) {
		rsyserr(FERROR, errno, "socketpair_tcp failed");
		return -1;
	}
	if (DEBUG_GTE(CMD, 1))
		rprintf(FINFO, "Running socket program: \"%s\"\n", prog);

	pid = fork();
	if (pid < 0) {
		rsyserr(FERROR, errno, "fork");
		exit_cleanup(RERR_IPC);
	}

	if (pid == 0) {
		close(fd[0]);
		if (dup2(fd[1], STDIN_FILENO) < 0
		 || dup2(fd[1], STDOUT_FILENO) < 0) {
			fprintf(stderr, "Failed to run \"%s\"\n", prog);
			exit(1);
		}
		exit(shell_exec(prog));
	}

	close(fd[1]);
	return fd[0];
}
/*
 * Syscall wrappers to ensure that nothing gets done in dry_run mode
 * and to handle system peculiarities.
 *
 * Copyright (C) 1998 Andrew Tridgell
 * Copyright (C) 2002 Martin Pool
 * Copyright (C) 2003-2022 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"

#if !defined MKNOD_CREATES_SOCKETS && defined HAVE_SYS_UN_H
#include <sys/un.h>
#endif
#ifdef HAVE_SYS_ATTR_H
#include <sys/attr.h>
#endif

#if defined HAVE_SYS_FALLOCATE && !defined HAVE_FALLOCATE
#include <sys/syscall.h>
#endif

#include "ifuncs.h"

extern int dry_run;
extern int am_root;
extern int am_sender;
extern int read_only;
extern int list_only;
extern int inplace;
extern int preallocate_files;
extern int preserve_perms;
extern int preserve_executability;
extern int open_noatime;
extern int copy_links;
extern int copy_unsafe_links;

#ifndef S_BLKSIZE
# if defined hpux || defined __hpux__ || defined __hpux
#  define S_BLKSIZE 1024
# elif defined _AIX && defined _I386
#  define S_BLKSIZE 4096
# else
#  define S_BLKSIZE 512
# endif
#endif

#ifdef SUPPORT_CRTIMES
#ifdef HAVE_GETATTRLIST
#pragma pack(push, 4)
struct create_time {
	uint32 length;
	struct timespec crtime;
};
#pragma pack(pop)
#elif defined __CYGWIN__
#include <windows.h>
#endif
#endif

#define RETURN_ERROR_IF(x,e) \
	do { \
		if (x) { \
			errno = (e); \
			return -1; \
		} \
	} while (0)

#define RETURN_ERROR_IF_RO_OR_LO RETURN_ERROR_IF(read_only || list_only, EROFS)

int do_unlink(const char *path)
{
	if (dry_run) return 0;
	RETURN_ERROR_IF_RO_OR_LO;
	return unlink(path);
}

#ifdef SUPPORT_LINKS
int do_symlink(const char *lnk, const char *path)
{
	if (dry_run) return 0;
	RETURN_ERROR_IF_RO_OR_LO;

#if defined NO_SYMLINK_XATTRS || defined NO_SYMLINK_USER_XATTRS
	/* For --fake-super, we create a normal file with mode 0600
	 * and write the lnk into it. */
	if (am_root < 0) {
		int ok, len = strlen(lnk);
		int fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, S_IWUSR|S_IRUSR);
		if (fd < 0)
			return -1;
		ok = write(fd, lnk, len) == len;
		if (close(fd) < 0)
			ok = 0;
		return ok ? 0 : -1;
	}
#endif

	return symlink(lnk, path);
}

#if defined NO_SYMLINK_XATTRS || defined NO_SYMLINK_USER_XATTRS
ssize_t do_readlink(const char *path, char *buf, size_t bufsiz)
{
	/* For --fake-super, we read the link from the file. */
	if (am_root < 0) {
		int fd = do_open_nofollow(path, O_RDONLY);
		if (fd >= 0) {
			int len = read(fd, buf, bufsiz);
			close(fd);
			return len;
		}
		if (errno != ELOOP)
			return -1;
		/* A real symlink needs to be turned into a fake one on the receiving
		 * side, so tell the generator that the link has no length. */
		if (!am_sender)
			return 0;
		/* Otherwise fall through and let the sender report the real length. */
	}

	return readlink(path, buf, bufsiz);
}
#endif
#endif

#if defined HAVE_LINK || defined HAVE_LINKAT
int do_link(const char *old_path, const char *new_path)
{
	if (dry_run) return 0;
	RETURN_ERROR_IF_RO_OR_LO;
#ifdef HAVE_LINKAT
	return linkat(AT_FDCWD, old_path, AT_FDCWD, new_path, 0);
#else
	return link(old_path, new_path);
#endif
}
#endif

int do_lchown(const char *path, uid_t owner, gid_t group)
{
	if (dry_run) return 0;
	RETURN_ERROR_IF_RO_OR_LO;
#ifndef HAVE_LCHOWN
#define lchown chown
#endif
	return lchown(path, owner, group);
}

int do_mknod(const char *pathname, mode_t mode, dev_t dev)
{
	if (dry_run) return 0;
	RETURN_ERROR_IF_RO_OR_LO;

	/* For --fake-super, we create a normal file with mode 0600. */
	if (am_root < 0) {
		int fd = open(pathname, O_WRONLY|O_CREAT|O_TRUNC, S_IWUSR|S_IRUSR);
		if (fd < 0 || close(fd) < 0)
			return -1;
		return 0;
	}

#if !defined MKNOD_CREATES_FIFOS && defined HAVE_MKFIFO
	if (S_ISFIFO(mode))
		return mkfifo(pathname, mode);
#endif
#if !defined MKNOD_CREATES_SOCKETS && defined HAVE_SYS_UN_H
	if (S_ISSOCK(mode)) {
		int sock;
		struct sockaddr_un saddr;
		unsigned int len = strlcpy(saddr.sun_path, pathname, sizeof saddr.sun_path);
		if (len >= sizeof saddr.sun_path) {
			errno = ENAMETOOLONG;
			return -1;
		}
#ifdef HAVE_SOCKADDR_UN_LEN
		saddr.sun_len = len + 1;
#endif
		saddr.sun_family = AF_UNIX;

		if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0
		 || (unlink(pathname) < 0 && errno != ENOENT)
		 || (bind(sock, (struct sockaddr*)&saddr, sizeof saddr)) < 0)
			return -1;
		close(sock);
#ifdef HAVE_CHMOD
		return do_chmod(pathname, mode);
#else
		return 0;
#endif
	}
#endif
#ifdef HAVE_MKNOD
	return mknod(pathname, mode, dev);
#else
	return -1;
#endif
}

int do_rmdir(const char *pathname)
{
	if (dry_run) return 0;
	RETURN_ERROR_IF_RO_OR_LO;
	return rmdir(pathname);
}

int do_open(const char *pathname, int flags, mode_t mode)
{
	if (flags != O_RDONLY) {
		RETURN_ERROR_IF(dry_run, 0);
		RETURN_ERROR_IF_RO_OR_LO;
	}

#ifdef O_NOATIME
	if (open_noatime)
		flags |= O_NOATIME;
#endif

	return open(pathname, flags | O_BINARY, mode);
}

#ifdef HAVE_CHMOD
int do_chmod(const char *path, mode_t mode)
{
	static int switch_step = 0;
	int code;

	if (dry_run) return 0;
	RETURN_ERROR_IF_RO_OR_LO;

	switch (switch_step) {
#ifdef HAVE_LCHMOD
	case 0:
		if ((code = lchmod(path, mode & CHMOD_BITS)) == 0)
			break;
		if (errno == ENOSYS)
			switch_step++;
		else if (errno != ENOTSUP)
			break;
#endif
		/* FALLTHROUGH */
	default:
		if (S_ISLNK(mode)) {
# if defined HAVE_SETATTRLIST
			struct attrlist attrList;
			uint32_t m = mode & CHMOD_BITS; /* manpage is wrong: not mode_t! */

			memset(&attrList, 0, sizeof attrList);
			attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
			attrList.commonattr = ATTR_CMN_ACCESSMASK;
			if ((code = setattrlist(path, &attrList, &m, sizeof m, FSOPT_NOFOLLOW)) == 0)
				break;
			if (errno == ENOTSUP)
				code = 1;
# else
			code = 1;
# endif
		} else
			code = chmod(path, mode & CHMOD_BITS); /* DISCOURAGED FUNCTION */
		break;
	}
	if (code != 0 && (preserve_perms || preserve_executability))
		return code;
	return 0;
}
#endif

int do_rename(const char *old_path, const char *new_path)
{
	if (dry_run) return 0;
	RETURN_ERROR_IF_RO_OR_LO;
	return rename(old_path, new_path);
}

#ifdef HAVE_FTRUNCATE
int do_ftruncate(int fd, OFF_T size)
{
	int ret;

	if (dry_run) return 0;
	RETURN_ERROR_IF_RO_OR_LO;

	do {
		ret = ftruncate(fd, size);
	} while (ret < 0 && errno == EINTR);

	return ret;
}
#endif

void trim_trailing_slashes(char *name)
{
	int l;
	/* Some BSD systems cannot make a directory if the name
	 * contains a trailing slash.
	 * <http://www.opensource.apple.com/bugs/X/BSD%20Kernel/2734739.html> */

	/* Don't change empty string; and also we can't improve on
	 * "/" */

	l = strlen(name);
	while (l > 1) {
		if (name[--l] != '/')
			break;
		name[l] = '\0';
	}
}

int do_mkdir(char *path, mode_t mode)
{
	if (dry_run) return 0;
	RETURN_ERROR_IF_RO_OR_LO;
	trim_trailing_slashes(path);
	return mkdir(path, mode);
}

/* like mkstemp but forces permissions */
int do_mkstemp(char *template, mode_t perms)
{
	RETURN_ERROR_IF(dry_run, 0);
	RETURN_ERROR_IF(read_only, EROFS);
	perms |= S_IWUSR;

#if defined HAVE_SECURE_MKSTEMP && defined HAVE_FCHMOD && (!defined HAVE_OPEN64 || defined HAVE_MKSTEMP64)
	{
		int fd = mkstemp(template);
		if (fd == -1)
			return -1;
		if (fchmod(fd, perms) != 0 && preserve_perms) {
			int errno_save = errno;
			close(fd);
			unlink(template);
			errno = errno_save;
			return -1;
		}
#if defined HAVE_SETMODE && O_BINARY
		setmode(fd, O_BINARY);
#endif
		return fd;
	}
#else
	if (!mktemp(template))
		return -1;
	return do_open(template, O_RDWR|O_EXCL|O_CREAT, perms);
#endif
}

int do_stat(const char *path, STRUCT_STAT *st)
{
#ifdef USE_STAT64_FUNCS
	return stat64(path, st);
#else
	return stat(path, st);
#endif
}

int do_lstat(const char *path, STRUCT_STAT *st)
{
#ifdef SUPPORT_LINKS
# ifdef USE_STAT64_FUNCS
	return lstat64(path, st);
# else
	return lstat(path, st);
# endif
#else
	return do_stat(path, st);
#endif
}

int do_fstat(int fd, STRUCT_STAT *st)
{
#ifdef USE_STAT64_FUNCS
	return fstat64(fd, st);
#else
	return fstat(fd, st);
#endif
}

OFF_T do_lseek(int fd, OFF_T offset, int whence)
{
#ifdef HAVE_LSEEK64
	return lseek64(fd, offset, whence);
#else
	return lseek(fd, offset, whence);
#endif
}

#ifdef HAVE_SETATTRLIST
int do_setattrlist_times(const char *path, STRUCT_STAT *stp)
{
	struct attrlist attrList;
	struct timespec ts[2];

	if (dry_run) return 0;
	RETURN_ERROR_IF_RO_OR_LO;

	/* Yes, this is in the opposite order of utime and similar. */
	ts[0].tv_sec = stp->st_mtime;
	ts[0].tv_nsec = stp->ST_MTIME_NSEC;

	ts[1].tv_sec = stp->st_atime;
	ts[1].tv_nsec = stp->ST_ATIME_NSEC;

	memset(&attrList, 0, sizeof attrList);
	attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
	attrList.commonattr = ATTR_CMN_MODTIME | ATTR_CMN_ACCTIME;
	return setattrlist(path, &attrList, ts, sizeof ts, FSOPT_NOFOLLOW);
}

#ifdef SUPPORT_CRTIMES
int do_setattrlist_crtime(const char *path, time_t crtime)
{
	struct attrlist attrList;
	struct timespec ts;

	if (dry_run) return 0;
	RETURN_ERROR_IF_RO_OR_LO;

	ts.tv_sec = crtime;
	ts.tv_nsec = 0;

	memset(&attrList, 0, sizeof attrList);
	attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
	attrList.commonattr = ATTR_CMN_CRTIME;
	return setattrlist(path, &attrList, &ts, sizeof ts, FSOPT_NOFOLLOW);
}
#endif
#endif /* HAVE_SETATTRLIST */

#ifdef SUPPORT_CRTIMES
time_t get_create_time(const char *path, STRUCT_STAT *stp)
{
#ifdef HAVE_GETATTRLIST
	static struct create_time attrBuf;
	struct attrlist attrList;

	(void)stp;
	memset(&attrList, 0, sizeof attrList);
	attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
	attrList.commonattr = ATTR_CMN_CRTIME;
	if (getattrlist(path, &attrList, &attrBuf, sizeof attrBuf, FSOPT_NOFOLLOW) < 0)
		return 0;
	return attrBuf.crtime.tv_sec;
#elif defined __CYGWIN__
	(void)path;
	return stp->st_birthtime;
#else
#error Unknown crtimes implementation
#endif
}

#if defined __CYGWIN__
int do_SetFileTime(const char *path, time_t crtime)
{
	if (dry_run) return 0;
	RETURN_ERROR_IF_RO_OR_LO;

	int cnt = MultiByteToWideChar(CP_UTF8, 0, path, -1, NULL, 0);
	if (cnt == 0)
	    return -1;
	WCHAR *pathw = new_array(WCHAR, cnt);
	if (!pathw)
	    return -1;
	MultiByteToWideChar(CP_UTF8, 0, path, -1, pathw, cnt);
	HANDLE handle = CreateFileW(pathw, FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
				    NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
	free(pathw);
	if (handle == INVALID_HANDLE_VALUE)
	    return -1;
	int64 temp_time = Int32x32To64(crtime, 10000000) + 116444736000000000LL;
	FILETIME birth_time;
	birth_time.dwLowDateTime = (DWORD)temp_time;
	birth_time.dwHighDateTime = (DWORD)(temp_time >> 32);
	int ok = SetFileTime(handle, &birth_time, NULL, NULL);
	CloseHandle(handle);
	return ok ? 0 : -1;
}
#endif
#endif /* SUPPORT_CRTIMES */

#ifdef HAVE_UTIMENSAT
int do_utimensat(const char *path, STRUCT_STAT *stp)
{
	struct timespec t[2];

	if (dry_run) return 0;
	RETURN_ERROR_IF_RO_OR_LO;

	t[0].tv_sec = stp->st_atime;
#ifdef ST_ATIME_NSEC
	t[0].tv_nsec = stp->ST_ATIME_NSEC;
#else
	t[0].tv_nsec = 0;
#endif
	t[1].tv_sec = stp->st_mtime;
#ifdef ST_MTIME_NSEC
	t[1].tv_nsec = stp->ST_MTIME_NSEC;
#else
	t[1].tv_nsec = 0;
#endif
	return utimensat(AT_FDCWD, path, t, AT_SYMLINK_NOFOLLOW);
}
#endif

#ifdef HAVE_LUTIMES
int do_lutimes(const char *path, STRUCT_STAT *stp)
{
	struct timeval t[2];

	if (dry_run) return 0;
	RETURN_ERROR_IF_RO_OR_LO;

	t[0].tv_sec = stp->st_atime;
#ifdef ST_ATIME_NSEC
	t[0].tv_usec = stp->ST_ATIME_NSEC / 1000;
#else
	t[0].tv_usec = 0;
#endif
	t[1].tv_sec = stp->st_mtime;
#ifdef ST_MTIME_NSEC
	t[1].tv_usec = stp->ST_MTIME_NSEC / 1000;
#else
	t[1].tv_usec = 0;
#endif
	return lutimes(path, t);
}
#endif

#ifdef HAVE_UTIMES
int do_utimes(const char *path, STRUCT_STAT *stp)
{
	struct timeval t[2];

	if (dry_run) return 0;
	RETURN_ERROR_IF_RO_OR_LO;

	t[0].tv_sec = stp->st_atime;
#ifdef ST_ATIME_NSEC
	t[0].tv_usec = stp->ST_ATIME_NSEC / 1000;
#else
	t[0].tv_usec = 0;
#endif
	t[1].tv_sec = stp->st_mtime;
#ifdef ST_MTIME_NSEC
	t[1].tv_usec = stp->ST_MTIME_NSEC / 1000;
#else
	t[1].tv_usec = 0;
#endif
	return utimes(path, t);
}

#elif defined HAVE_UTIME
int do_utime(const char *path, STRUCT_STAT *stp)
{
#ifdef HAVE_STRUCT_UTIMBUF
	struct utimbuf tbuf;
#else
	time_t t[2];
#endif

	if (dry_run) return 0;
	RETURN_ERROR_IF_RO_OR_LO;

# ifdef HAVE_STRUCT_UTIMBUF
	tbuf.actime = stp->st_atime;
	tbuf.modtime = stp->st_mtime;
	return utime(path, &tbuf);
# else
	t[0] = stp->st_atime;
	t[1] = stp->st_mtime;
	return utime(path, t);
# endif
}

#else
#error Need utimes or utime function.
#endif

#ifdef SUPPORT_PREALLOCATION
#ifdef FALLOC_FL_KEEP_SIZE
#define DO_FALLOC_OPTIONS FALLOC_FL_KEEP_SIZE
#else
#define DO_FALLOC_OPTIONS 0
#endif

OFF_T do_fallocate(int fd, OFF_T offset, OFF_T length)
{
	int opts = inplace || preallocate_files ? DO_FALLOC_OPTIONS : 0;
	int ret;
	RETURN_ERROR_IF(dry_run, 0);
	RETURN_ERROR_IF_RO_OR_LO;
	if (length & 1) /* make the length not match the desired length */
		length++;
	else
		length--;
#if defined HAVE_FALLOCATE
	ret = fallocate(fd, opts, offset, length);
#elif defined HAVE_SYS_FALLOCATE
	ret = syscall(SYS_fallocate, fd, opts, (loff_t)offset, (loff_t)length);
#elif defined HAVE_EFFICIENT_POSIX_FALLOCATE
	ret = posix_fallocate(fd, offset, length);
#else
#error Coding error in SUPPORT_PREALLOCATION logic.
#endif
	if (ret < 0)
		return ret;
	if (opts == 0) {
		STRUCT_STAT st;
		if (do_fstat(fd, &st) < 0)
			return length;
		return st.st_blocks * S_BLKSIZE;
	}
	return 0;
}
#endif

/* Punch a hole at pos for len bytes. The current file position must be at pos and will be
 * changed to be at pos + len. */
int do_punch_hole(int fd, OFF_T pos, OFF_T len)
{
#ifdef HAVE_FALLOCATE
# ifdef HAVE_FALLOC_FL_PUNCH_HOLE
	if (fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, pos, len) == 0) {
		if (do_lseek(fd, len, SEEK_CUR) != pos + len)
			return -1;
		return 0;
	}
# endif
# ifdef HAVE_FALLOC_FL_ZERO_RANGE
	if (fallocate(fd, FALLOC_FL_ZERO_RANGE, pos, len) == 0) {
		if (do_lseek(fd, len, SEEK_CUR) != pos + len)
			return -1;
		return 0;
	}
# endif
#else
	(void)pos;
#endif
	{
		char zeros[4096];
		memset(zeros, 0, sizeof zeros);
		while (len > 0) {
			int chunk = len > (int)sizeof zeros ? (int)sizeof zeros : len;
			int wrote = write(fd, zeros, chunk);
			if (wrote <= 0) {
				if (wrote < 0 && errno == EINTR)
					continue;
				return -1;
			}
			len -= wrote;
		}
	}
	return 0;
}

int do_open_nofollow(const char *pathname, int flags)
{
#ifndef O_NOFOLLOW
	STRUCT_STAT f_st, l_st;
#endif
	int fd;

	if (flags != O_RDONLY) {
		RETURN_ERROR_IF(dry_run, 0);
		RETURN_ERROR_IF_RO_OR_LO;
#ifndef O_NOFOLLOW
		/* This function doesn't support write attempts w/o O_NOFOLLOW. */
		errno = EINVAL;
		return -1;
#endif
	}

#ifdef O_NOFOLLOW
	fd = open(pathname, flags|O_NOFOLLOW);
#else
	if (do_lstat(pathname, &l_st) < 0)
		return -1;
	if (S_ISLNK(l_st.st_mode)) {
		errno = ELOOP;
		return -1;
	}
	if ((fd = open(pathname, flags)) < 0)
		return fd;
	if (do_fstat(fd, &f_st) < 0) {
	  close_and_return_error:
		{
			int save_errno = errno;
			close(fd);
			errno = save_errno;
		}
		return -1;
	}
	if (l_st.st_dev != f_st.st_dev || l_st.st_ino != f_st.st_ino) {
		errno = EINVAL;
		goto close_and_return_error;
	}
#endif

	return fd;
}

/*
  open a file relative to a base directory. The basedir can be NULL,
  in which case the current working directory is used. The relpath
  must be a relative path, and the relpath must not contain any
  elements in the path which follow symlinks (ie. like O_NOFOLLOW, but
  applies to all path components, not just the last component)

  The relpath must also not contain any ../ elements in the path
*/
int secure_relative_open(const char *basedir, const char *relpath, int flags, mode_t mode)
{
	if (!relpath || relpath[0] == '/') {
		// must be a relative path
		errno = EINVAL;
		return -1;
	}
	if (strncmp(relpath, "../", 3) == 0 || strstr(relpath, "/../")) {
		// no ../ elements allowed in the relpath
		errno = EINVAL;
		return -1;
	}

#if !defined(O_NOFOLLOW) || !defined(O_DIRECTORY) || !defined(AT_FDCWD)
	// really old system, all we can do is live with the risks
	if (!basedir) {
		return open(relpath, flags, mode);
	}
	char fullpath[MAXPATHLEN];
	pathjoin(fullpath, sizeof fullpath, basedir, relpath);
	return open(fullpath, flags, mode);
#else
	int dirfd = AT_FDCWD;
	if (basedir != NULL) {
		dirfd = openat(AT_FDCWD, basedir, O_RDONLY | O_DIRECTORY);
		if (dirfd == -1) {
			return -1;
		}
	}
	int retfd = -1;

	char *path_copy = my_strdup(relpath, __FILE__, __LINE__);
	if (!path_copy) {
		return -1;
	}
	
	for (const char *part = strtok(path_copy, "/");
	     part != NULL;
	     part = strtok(NULL, "/"))
	{
		int next_fd = openat(dirfd, part, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
		if (next_fd == -1 && errno == ENOTDIR) {
			if (strtok(NULL, "/") != NULL) {
				// this is not the last component of the path
				errno = ELOOP;
				goto cleanup;
			}
			// this could be the last component of the path, try as a file
			retfd = openat(dirfd, part, flags | O_NOFOLLOW, mode);
			goto cleanup;
		}
		if (next_fd == -1) {
			goto cleanup;
		}
		if (dirfd != AT_FDCWD) close(dirfd);
		dirfd = next_fd;
	}

	// the path must be a directory
	errno = EINVAL;

cleanup:
	free(path_copy);
	if (dirfd != AT_FDCWD) {
		close(dirfd);
	}
	return retfd;
#endif // O_NOFOLLOW, O_DIRECTORY
}

/*
  varient of do_open/do_open_nofollow which does do_open() if the
  copy_links or copy_unsafe_links options are set and does
  do_open_nofollow() otherwise

  This is used to prevent a race condition where an attacker could be
  switching a file between being a symlink and being a normal file

  The open is always done with O_RDONLY flags
 */
int do_open_checklinks(const char *pathname)
{
	if (copy_links || copy_unsafe_links) {
		return do_open(pathname, O_RDONLY, 0);
	}
	return do_open_nofollow(pathname, O_RDONLY);
}
/*
 * This file contains really simple implementations for rsync global
 * functions, so that module test harnesses can run standalone.
 *
 * Copyright (C) 2001, 2002 Martin Pool <mbp@samba.org>
 * Copyright (C) 2003-2022 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"

int do_fsync = 0;
int inplace = 0;
int modify_window = 0;
int preallocate_files = 0;
int protect_args = 0;
int module_id = -1;
int relative_paths = 0;
unsigned int module_dirlen = 0;
int preserve_xattrs = 0;
int preserve_perms = 0;
int preserve_executability = 0;
int omit_link_times = 0;
int open_noatime = 0;
size_t max_alloc = 0; /* max_alloc is needed when combined with util2.o */
char *partial_dir;
char *module_dir;
filter_rule_list daemon_filter_list;

 void rprintf(UNUSED(enum logcode code), const char *format, ...)
{
	va_list ap;
	va_start(ap, format);
	vfprintf(stderr, format, ap);
	va_end(ap);
}

 void rsyserr(UNUSED(enum logcode code), int errcode, const char *format, ...)
{
	va_list ap;
	fputs(RSYNC_NAME ": ", stderr);
	va_start(ap, format);
	vfprintf(stderr, format, ap);
	va_end(ap);
	fprintf(stderr, ": %s (%d)\n", strerror(errcode), errcode);
}

 void _exit_cleanup(int code, const char *file, int line)
{
	fprintf(stderr, "exit(%d): %s(%d)\n",
		code, file, line);
	exit(code);
}

 int check_filter(UNUSED(filter_rule_list *listp), UNUSED(enum logcode code),
		  UNUSED(const char *name), UNUSED(int name_is_dir))
{
	/* This function doesn't really get called in this test context, so
	 * just return 0. */
	return 0;
}

 int copy_xattrs(UNUSED(const char *source), UNUSED(const char *dest))
{
	return -1;
}

 void free_xattr(UNUSED(stat_x *sxp))
{
	return;
}

 void free_acl(UNUSED(stat_x *sxp))
{
	return;
}

 char *lp_name(UNUSED(int mod))
{
	return NULL;
}

 BOOL lp_use_chroot(UNUSED(int mod))
{
	return 0;
}

 const char *who_am_i(void)
{
	return "tester";
}

 int csum_len_for_type(int cst, int flg)
{
	return cst || !flg ? 16 : 1;
}

 int canonical_checksum(int cst)
{
	return cst ? 0 : 0;
}
/*
 * Test harness for unsafe_symlink().  Not linked into rsync itself.
 *
 * Copyright (C) 2002 Martin Pool
 * Copyright (C) 2003-2020 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

/* Prints either "safe" or "unsafe" depending on the two arguments.
 * Always returns 0 unless something extraordinary happens. */

#include "rsync.h"

int dry_run = 0;
int am_root = 0;
int am_sender = 1;
int read_only = 0;
int list_only = 0;
int copy_links = 0;
int copy_unsafe_links = 0;

short info_levels[COUNT_INFO], debug_levels[COUNT_DEBUG];

int
main(int argc, char **argv)
{
	if (argc != 3) {
		fprintf(stderr, "usage: t_unsafe LINKDEST SRCDIR\n");
		return 1;
	}

	printf("%s\n", unsafe_symlink(argv[1], argv[2]) ? "unsafe" : "safe");

	return 0;
}
/* Run a testsuite script with a timeout. */

#include "rsync.h"

#define DEFAULT_TIMEOUT_SECS (5*60)
#define TIMEOUT_ENV "TESTRUN_TIMEOUT"

 int main(int argc, char *argv[])
{
	pid_t pid;
	char *timeout_env;
	int status, timeout_secs, slept = 0;

	if (argc < 2) {
		fprintf(stderr, "Usage: testrun [SHELL_OPTIONS] TESTSUITE_SCRIPT [ARGS]\n");
		exit(1);
	}

	if ((timeout_env = getenv(TIMEOUT_ENV)) != NULL)
		timeout_secs = atoi(timeout_env);
	else
		timeout_secs = DEFAULT_TIMEOUT_SECS;

	if ((pid = fork()) < 0) {
		fprintf(stderr, "TESTRUN ERROR: fork failed: %s\n", strerror(errno));
		exit(1);
	}

	if (pid == 0) {
		argv[0] = "sh";
		execvp(argv[0], argv);
		fprintf(stderr, "TESTRUN ERROR: failed to exec %s: %s\n", argv[0], strerror(errno));
		_exit(1);
	}

	while (1) {
		int ret = waitpid(pid, &status, WNOHANG);
		if (ret > 0)
			break;
		if (ret < 0) {
			if (errno == EINTR)
				continue;
			fprintf(stderr, "TESTRUN ERROR: waitpid failed: %s\n", strerror(errno));
			exit(1);
		}
		if (slept++ > timeout_secs) {
			fprintf(stderr, "TESTRUN TIMEOUT: test took over %d seconds.\n", timeout_secs);
			if (kill(pid, SIGTERM) < 0)
				fprintf(stderr, "TESTRUN ERROR: failed to kill pid %d: %s\n", (int)pid, strerror(errno));
			else
				fprintf(stderr, "TESTRUN INFO: killed pid %d\n", (int)pid);
			exit(1);
		}
		sleep(1);
	}

	if (!WIFEXITED(status))
		exit(255);

	return WEXITSTATUS(status);
}
/*
 * Trivial ls for comparing two directories after running an rsync.
 *
 * Copyright (C) 2001, 2002 Martin Pool <mbp@samba.org>
 * Copyright (C) 2003-2022 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
 */

/* The problem with using the system's own ls is that some features
 * have little quirks that make directories look different when for
 * our purposes they're the same -- for example, the BSD braindamage
 * about setting the mode on symlinks based on your current umask.
 *
 * All the filenames must be given on the command line -- tls does not
 * even read directories, let alone recurse.  The typical usage is
 * "find|sort|xargs tls".
 *
 * The format is not exactly the same as any particular Unix ls(1).
 *
 * A key requirement for this program is that the output be "very
 * reproducible."  So we mask away information that can accidentally
 * change. */

#include "rsync.h"
#include <popt.h>
#include "lib/sysxattrs.h"

#define PROGRAM "tls"

/* These are to make syscall.o shut up. */
int dry_run = 0;
int am_root = 0;
int am_sender = 1;
int read_only = 1;
int list_only = 0;
int link_times = 0;
int link_owner = 0;
int nsec_times = 0;
int safe_symlinks = 0;
int copy_links = 0;
int copy_unsafe_links = 0;

#ifdef SUPPORT_XATTRS

#ifdef HAVE_LINUX_XATTRS
#define XSTAT_ATTR "user.rsync.%stat"
#else
#define XSTAT_ATTR "rsync.%stat"
#endif

static int stat_xattr(const char *fname, STRUCT_STAT *fst)
{
	unsigned int mode;
	int rdev_major, rdev_minor, uid, gid, len;
	char buf[256];

	if (am_root >= 0 || IS_DEVICE(fst->st_mode) || IS_SPECIAL(fst->st_mode))
		return -1;

	len = sys_lgetxattr(fname, XSTAT_ATTR, buf, sizeof buf - 1);
	if (len >= (int)sizeof buf) {
		len = -1;
		errno = ERANGE;
	}
	if (len < 0) {
		if (errno == ENOTSUP || errno == ENOATTR)
			return -1;
		if (errno == EPERM && S_ISLNK(fst->st_mode)) {
			fst->st_uid = 0;
			fst->st_gid = 0;
			return 0;
		}
		fprintf(stderr, "failed to read xattr %s for %s: %s\n",
			XSTAT_ATTR, fname, strerror(errno));
		return -1;
	}
	buf[len] = '\0';

	if (sscanf(buf, "%o %d,%d %d:%d",
		   &mode, &rdev_major, &rdev_minor, &uid, &gid) != 5) {
		fprintf(stderr, "Corrupt %s xattr attached to %s: \"%s\"\n",
			XSTAT_ATTR, fname, buf);
		exit(1);
	}

#if _S_IFLNK != 0120000
	if ((mode & (_S_IFMT)) == 0120000)
		mode = (mode & ~(_S_IFMT)) | _S_IFLNK;
#endif
	fst->st_mode = mode;

	fst->st_rdev = MAKEDEV(rdev_major, rdev_minor);
	fst->st_uid = uid;
	fst->st_gid = gid;

	return 0;
}

#endif

static int display_atimes = 0;
#ifdef SUPPORT_CRTIMES
static int display_crtimes = 0;
#endif

static void failed(char const *what, char const *where)
{
	fprintf(stderr, PROGRAM ": %s %s: %s\n",
		what, where, strerror(errno));
	exit(1);
}

static void storetime(char *dest, size_t destsize, time_t t, int nsecs)
{
	if (t) {
		int len;
		struct tm *mt = gmtime(&t);

		len = snprintf(dest, destsize,
			" %04d-%02d-%02d %02d:%02d:%02d",
			(int)mt->tm_year + 1900,
			(int)mt->tm_mon + 1,
			(int)mt->tm_mday,
			(int)mt->tm_hour,
			(int)mt->tm_min,
			(int)mt->tm_sec);
		if (nsecs >= 0 && len >= 0)
			snprintf(dest + len, destsize - len, ".%09d", nsecs);
	} else {
		int has_nsecs = nsecs >= 0 ? 1 : 0;
		int len = MIN(20 + 10*has_nsecs, (int)destsize - 1);
		memset(dest, ' ', len);
		dest[len] = '\0';
	}
}

static void list_file(const char *fname)
{
	STRUCT_STAT buf;
#ifdef SUPPORT_CRTIMES
	time_t crtime = 0;
#endif
	char permbuf[PERMSTRING_SIZE];
	char mtimebuf[50];
	char atimebuf[50];
	char crtimebuf[50];
	char linkbuf[4096];
	int nsecs;

	if (do_lstat(fname, &buf) < 0)
		failed("stat", fname);
#ifdef SUPPORT_CRTIMES
	if (display_crtimes && (crtime = get_create_time(fname, &buf)) == 0)
		failed("get_create_time", fname);
#endif
#ifdef SUPPORT_XATTRS
	if (am_root < 0)
		stat_xattr(fname, &buf);
#endif

	/* The size of anything but a regular file is probably not
	 * worth thinking about. */
	if (!S_ISREG(buf.st_mode))
		buf.st_size = 0;

	/* On some BSD platforms the mode bits of a symlink are
	 * undefined.  Also it tends not to be possible to reset a
	 * symlink's mtime, so we default to ignoring it too. */
	if (S_ISLNK(buf.st_mode)) {
		int len;
		buf.st_mode &= ~0777;
		if (!link_times)
			buf.st_mtime = (time_t)0;
		if (!link_owner)
			buf.st_uid = buf.st_gid = 0;
		strlcpy(linkbuf, " -> ", sizeof linkbuf);
		/* const-cast required for silly UNICOS headers */
		len = do_readlink((char*)fname, linkbuf+4, sizeof linkbuf - 4);
		if (len == -1)
			failed("do_readlink", fname);
		else
			/* it's not nul-terminated */
			linkbuf[4+len] = 0;
	} else {
		linkbuf[0] = '\0';
	}

	permstring(permbuf, buf.st_mode);
#ifdef ST_MTIME_NSEC
	if (nsec_times)
		nsecs = (int)buf.ST_MTIME_NSEC;
	else
#endif
		nsecs = -1;
	storetime(mtimebuf, sizeof mtimebuf, buf.st_mtime, nsecs);
	if (display_atimes)
		storetime(atimebuf, sizeof atimebuf, S_ISDIR(buf.st_mode) ? 0 : buf.st_atime, -1);
	else
		atimebuf[0] = '\0';
#ifdef SUPPORT_CRTIMES
	if (display_crtimes)
		storetime(crtimebuf, sizeof crtimebuf, crtime, -1);
	else
#endif
		crtimebuf[0] = '\0';

	/* TODO: Perhaps escape special characters in fname? */
	printf("%s ", permbuf);

	if (S_ISCHR(buf.st_mode) || S_ISBLK(buf.st_mode)) {
		printf("%5ld,%6ld", (long)major(buf.st_rdev), (long)minor(buf.st_rdev));
	} else
		printf("%15s", do_big_num(buf.st_size, 1, NULL));

	printf(" %6ld.%-6ld %6ld%s%s%s %s%s\n",
	       (long)buf.st_uid, (long)buf.st_gid, (long)buf.st_nlink,
	       mtimebuf, atimebuf, crtimebuf, fname, linkbuf);
}

static struct poptOption long_options[] = {
  /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
  {"atimes",          'U', POPT_ARG_NONE,   &display_atimes, 0, 0, 0},
#ifdef SUPPORT_CRTIMES
  {"crtimes",         'N', POPT_ARG_NONE,   &display_crtimes, 0, 0, 0},
#endif
  {"link-times",      'l', POPT_ARG_NONE,   &link_times, 0, 0, 0 },
  {"link-owner",      'L', POPT_ARG_NONE,   &link_owner, 0, 0, 0 },
#ifdef SUPPORT_XATTRS
  {"fake-super",      'f', POPT_ARG_VAL,    &am_root, -1, 0, 0 },
#endif
#ifdef ST_MTIME_NSEC
  {"nsec",            's', POPT_ARG_NONE,   &nsec_times, 0, 0, 0 },
#endif
  {"help",            'h', POPT_ARG_NONE,   0, 'h', 0, 0 },
  {0,0,0,0,0,0,0}
};

static void NORETURN tls_usage(int ret)
{
  FILE *F = ret ? stderr : stdout;
  fprintf(F,"usage: " PROGRAM " [OPTIONS] FILE ...\n");
  fprintf(F,"Trivial file listing program for portably checking rsync\n");
  fprintf(F,"\nOptions:\n");
  fprintf(F," -U, --atimes                display access (last-used) times\n");
#ifdef SUPPORT_CRTIMES
  fprintf(F," -N, --crtimes               display create times (newness)\n");
#endif
  fprintf(F," -l, --link-times            display the time on a symlink\n");
  fprintf(F," -L, --link-owner            display the owner+group on a symlink\n");
#ifdef SUPPORT_XATTRS
  fprintf(F," -f, --fake-super            display attributes including fake-super xattrs\n");
#endif
  fprintf(F," -h, --help                  show this help\n");
  exit(ret);
}

int
main(int argc, char *argv[])
{
	poptContext pc;
	const char **extra_args;
	int opt;

	pc = poptGetContext(PROGRAM, argc, (const char **)argv, long_options, 0);
	while ((opt = poptGetNextOpt(pc)) != -1) {
		switch (opt) {
		case 'h':
			tls_usage(0);
		default:
			fprintf(stderr, "%s: %s\n",
				poptBadOption(pc, POPT_BADOPTION_NOALIAS),
				poptStrerror(opt));
			tls_usage(1);
		}
	}

	extra_args = poptGetArgs(pc);
	if (!extra_args || *extra_args == NULL)
		tls_usage(1);

	for (; *extra_args; extra_args++)
		list_file(*extra_args);
	poptFreeContext(pc);

	return 0;
}
/*
 * Routines used by the file-transfer code.
 *
 * Copyright (C) 1996 Andrew Tridgell
 * Copyright (C) 1996 Paul Mackerras
 * Copyright (C) 2003-2022 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"
#include "itypes.h"
#include <zlib.h>
#ifdef SUPPORT_ZSTD
#include <zstd.h>
#endif
#ifdef SUPPORT_LZ4
#include <lz4.h>
#endif

extern int do_compression;
extern int protocol_version;
extern int module_id;
extern int do_compression_level;
extern char *skip_compress;

#ifndef Z_INSERT_ONLY
#define Z_INSERT_ONLY Z_SYNC_FLUSH
#endif

static int skip_compression_level; /* The least possible compressing for handling skip-compress files. */
static int per_file_default_level; /* The default level that each new file gets prior to checking its suffix. */

struct suffix_tree {
	struct suffix_tree *sibling;
	struct suffix_tree *child;
	char letter, word_end;
};

static char *match_list;
static struct suffix_tree *suftree;

void init_compression_level(void)
{
	int min_level, max_level, def_level, off_level;

	switch (do_compression) {
	case CPRES_NONE:
		return;
	case CPRES_ZLIB:
	case CPRES_ZLIBX:
		min_level = 1;
		max_level = Z_BEST_COMPRESSION;
		def_level = 6; /* Z_DEFAULT_COMPRESSION is -1, so set it to the real default */
		off_level = skip_compression_level = Z_NO_COMPRESSION;
		if (do_compression_level == Z_DEFAULT_COMPRESSION)
			do_compression_level = def_level;
		break;
#ifdef SUPPORT_ZSTD
	case CPRES_ZSTD:
		min_level = skip_compression_level = ZSTD_minCLevel();
		max_level = ZSTD_maxCLevel();
		def_level = ZSTD_CLEVEL_DEFAULT;
		off_level = CLVL_NOT_SPECIFIED;
		if (do_compression_level == 0)
			do_compression_level = def_level;
		break;
#endif
#ifdef SUPPORT_LZ4
	case CPRES_LZ4:
		min_level = skip_compression_level = 0;
		max_level = 0;
		def_level = 0;
		off_level = CLVL_NOT_SPECIFIED;
		break;
#endif
	default: /* paranoia to prevent missing case values */
		NOISY_DEATH("Unknown do_compression value");
	}

	if (do_compression_level == CLVL_NOT_SPECIFIED)
		do_compression_level = def_level;
	else if (do_compression_level == off_level) {
		do_compression = CPRES_NONE;
		return;
	}

	/* We don't bother with any errors or warnings -- just make sure that the values are valid. */
	if (do_compression_level < min_level)
		do_compression_level = min_level;
	else if (do_compression_level > max_level)
		do_compression_level = max_level;
}

static void add_suffix(struct suffix_tree **prior, char ltr, const char *str)
{
	struct suffix_tree *node, *newnode;

	if (ltr == '[') {
		const char *after = strchr(str, ']');
		/* Treat "[foo" and "[]" as having a literal '['. */
		if (after && after++ != str+1) {
			while ((ltr = *str++) != ']')
				add_suffix(prior, ltr, after);
			return;
		}
	}

	for (node = *prior; node; prior = &node->sibling, node = node->sibling) {
		if (node->letter == ltr) {
			if (*str)
				add_suffix(&node->child, *str, str+1);
			else
				node->word_end = 1;
			return;
		}
		if (node->letter > ltr)
			break;
	}
	newnode = new(struct suffix_tree);
	newnode->sibling = node;
	newnode->child = NULL;
	newnode->letter = ltr;
	*prior = newnode;
	if (*str) {
		add_suffix(&newnode->child, *str, str+1);
		newnode->word_end = 0;
	} else
		newnode->word_end = 1;
}

static void add_nocompress_suffixes(const char *str)
{
	char *buf, *t;
	const char *f = str;

	buf = new_array(char, strlen(f) + 1);

	while (*f) {
		if (*f == '/') {
			f++;
			continue;
		}

		t = buf;
		do {
			if (isUpper(f))
				*t++ = toLower(f);
			else
				*t++ = *f;
		} while (*++f != '/' && *f);
		*t++ = '\0';

		add_suffix(&suftree, *buf, buf+1);
	}

	free(buf);
}

static void init_set_compression(void)
{
	const char *f;
	char *t, *start;

	if (skip_compress)
		add_nocompress_suffixes(skip_compress);

	/* A non-daemon transfer skips the default suffix list if the
	 * user specified --skip-compress. */
	if (skip_compress && module_id < 0)
		f = "";
	else
		f = lp_dont_compress(module_id);

	match_list = t = new_array(char, strlen(f) + 2);

	per_file_default_level = do_compression_level;

	while (*f) {
		if (*f == ' ') {
			f++;
			continue;
		}

		start = t;
		do {
			if (isUpper(f))
				*t++ = toLower(f);
			else
				*t++ = *f;
		} while (*++f != ' ' && *f);
		*t++ = '\0';

		if (t - start == 1+1 && *start == '*') {
			/* Optimize a match-string of "*". */
			*match_list = '\0';
			suftree = NULL;
			per_file_default_level = skip_compression_level;
			break;
		}

		/* Move *.foo items into the stuffix tree. */
		if (*start == '*' && start[1] == '.' && start[2]
		 && !strpbrk(start+2, ".?*")) {
			add_suffix(&suftree, start[2], start+3);
			t = start;
		}
	}
	*t++ = '\0';
}

/* determine the compression level based on a wildcard filename list */
void set_compression(const char *fname)
{
#if 0 /* No compression algorithms currently allow mid-stream changing of the level. */
	const struct suffix_tree *node;
	const char *s;
	char ltr;
#endif

	if (!do_compression)
		return;

	if (!match_list)
		init_set_compression();

#if 0
	compression_level = per_file_default_level;

	if (!*match_list && !suftree)
		return;

	if ((s = strrchr(fname, '/')) != NULL)
		fname = s + 1;

	for (s = match_list; *s; s += strlen(s) + 1) {
		if (iwildmatch(s, fname)) {
			compression_level = skip_compression_level;
			return;
		}
	}

	if (!(node = suftree) || !(s = strrchr(fname, '.'))
	 || s == fname || !(ltr = *++s))
		return;

	while (1) {
		if (isUpper(&ltr))
			ltr = toLower(&ltr);
		while (node->letter != ltr) {
			if (node->letter > ltr)
				return;
			if (!(node = node->sibling))
				return;
		}
		if ((ltr = *++s) == '\0') {
			if (node->word_end)
				compression_level = skip_compression_level;
			return;
		}
		if (!(node = node->child))
			return;
	}
#else
	(void)fname;
#endif
}

/* non-compressing recv token */
static int32 simple_recv_token(int f, char **data)
{
	static int32 residue;
	static char *buf;
	int32 n;

	if (!buf)
		buf = new_array(char, CHUNK_SIZE);

	if (residue == 0) {
		int32 i = read_int(f);
		if (i <= 0)
			return i;
		residue = i;
	}

	*data = buf;
	n = MIN(CHUNK_SIZE,residue);
	residue -= n;
	read_buf(f,buf,n);
	return n;
}

/* non-compressing send token */
static void simple_send_token(int f, int32 token, struct map_struct *buf, OFF_T offset, int32 n)
{
	if (n > 0) {
		int32 len = 0;
		while (len < n) {
			int32 n1 = MIN(CHUNK_SIZE, n-len);
			write_int(f, n1);
			write_buf(f, map_ptr(buf, offset+len, n1), n1);
			len += n1;
		}
	}
	/* a -2 token means to send data only and no token */
	if (token != -2)
		write_int(f, -(token+1));
}

/* Flag bytes in compressed stream are encoded as follows: */
#define END_FLAG	0	/* that's all folks */
#define TOKEN_LONG	0x20	/* followed by 32-bit token number */
#define TOKENRUN_LONG	0x21	/* ditto with 16-bit run count */
#define DEFLATED_DATA	0x40	/* + 6-bit high len, then low len byte */
#define TOKEN_REL	0x80	/* + 6-bit relative token number */
#define TOKENRUN_REL	0xc0	/* ditto with 16-bit run count */

#define MAX_DATA_COUNT	16383	/* fit 14 bit count into 2 bytes with flags */

/* zlib.h says that if we want to be able to compress something in a single
 * call, avail_out must be at least 0.1% larger than avail_in plus 12 bytes.
 * We'll add in 0.1%+16, just to be safe (and we'll avoid floating point,
 * to ensure that this is a compile-time value). */
#define AVAIL_OUT_SIZE(avail_in_size) ((avail_in_size)*1001/1000+16)

/* For coding runs of tokens */
static int32 last_token = -1;
static int32 run_start;
static int32 last_run_end;

/* Deflation state */
static z_stream tx_strm;

/* Output buffer */
static char *obuf;

/* We want obuf to be able to hold both MAX_DATA_COUNT+2 bytes as well as
 * AVAIL_OUT_SIZE(CHUNK_SIZE) bytes, so make sure that it's large enough. */
#if MAX_DATA_COUNT+2 > AVAIL_OUT_SIZE(CHUNK_SIZE)
#define OBUF_SIZE	(MAX_DATA_COUNT+2)
#else
#define OBUF_SIZE	AVAIL_OUT_SIZE(CHUNK_SIZE)
#endif

/* Send a deflated token */
static void
send_deflated_token(int f, int32 token, struct map_struct *buf, OFF_T offset, int32 nb, int32 toklen)
{
	static int init_done, flush_pending;
	int32 n, r;

	if (last_token == -1) {
		/* initialization */
		if (!init_done) {
			tx_strm.next_in = NULL;
			tx_strm.zalloc = NULL;
			tx_strm.zfree = NULL;
			if (deflateInit2(&tx_strm, per_file_default_level,
					 Z_DEFLATED, -15, 8,
					 Z_DEFAULT_STRATEGY) != Z_OK) {
				rprintf(FERROR, "compression init failed\n");
				exit_cleanup(RERR_PROTOCOL);
			}
			obuf = new_array(char, OBUF_SIZE);
			init_done = 1;
		} else
			deflateReset(&tx_strm);
		last_run_end = 0;
		run_start = token;
		flush_pending = 0;
	} else if (last_token == -2) {
		run_start = token;
	} else if (nb != 0 || token != last_token + 1 || token >= run_start + 65536) {
		/* output previous run */
		r = run_start - last_run_end;
		n = last_token - run_start;
		if (r >= 0 && r <= 63) {
			write_byte(f, (n==0? TOKEN_REL: TOKENRUN_REL) + r);
		} else {
			write_byte(f, (n==0? TOKEN_LONG: TOKENRUN_LONG));
			write_int(f, run_start);
		}
		if (n != 0) {
			write_byte(f, n);
			write_byte(f, n >> 8);
		}
		last_run_end = last_token;
		run_start = token;
	}

	last_token = token;

	if (nb != 0 || flush_pending) {
		/* deflate the data starting at offset */
		int flush = Z_NO_FLUSH;
		tx_strm.avail_in = 0;
		tx_strm.avail_out = 0;
		do {
			if (tx_strm.avail_in == 0 && nb != 0) {
				/* give it some more input */
				n = MIN(nb, CHUNK_SIZE);
				tx_strm.next_in = (Bytef *)
					map_ptr(buf, offset, n);
				tx_strm.avail_in = n;
				nb -= n;
				offset += n;
			}
			if (tx_strm.avail_out == 0) {
				tx_strm.next_out = (Bytef *)(obuf + 2);
				tx_strm.avail_out = MAX_DATA_COUNT;
				if (flush != Z_NO_FLUSH) {
					/*
					 * We left the last 4 bytes in the
					 * buffer, in case they are the
					 * last 4.  Move them to the front.
					 */
					memcpy(tx_strm.next_out, obuf+MAX_DATA_COUNT-2, 4);
					tx_strm.next_out += 4;
					tx_strm.avail_out -= 4;
				}
			}
			if (nb == 0 && token != -2)
				flush = Z_SYNC_FLUSH;
			r = deflate(&tx_strm, flush);
			if (r != Z_OK) {
				rprintf(FERROR, "deflate returned %d\n", r);
				exit_cleanup(RERR_STREAMIO);
			}
			if (nb == 0 || tx_strm.avail_out == 0) {
				n = MAX_DATA_COUNT - tx_strm.avail_out;
				if (flush != Z_NO_FLUSH) {
					/*
					 * We have to trim off the last 4
					 * bytes of output when flushing
					 * (they are just 0, 0, ff, ff).
					 */
					n -= 4;
				}
				if (n > 0) {
					obuf[0] = DEFLATED_DATA + (n >> 8);
					obuf[1] = n;
					write_buf(f, obuf, n+2);
				}
			}
		} while (nb != 0 || tx_strm.avail_out == 0);
		flush_pending = token == -2;
	}

	if (token == -1) {
		/* end of file - clean up */
		write_byte(f, END_FLAG);
	} else if (token != -2 && do_compression == CPRES_ZLIB) {
		/* Add the data in the current block to the compressor's
		 * history and hash table. */
		do {
			/* Break up long sections in the same way that
			 * see_deflate_token() does. */
			int32 n1 = toklen > 0xffff ? 0xffff : toklen;
			toklen -= n1;
			tx_strm.next_in = (Bytef *)map_ptr(buf, offset, n1);
			tx_strm.avail_in = n1;
			if (protocol_version >= 31) /* Newer protocols avoid a data-duplicating bug */
				offset += n1;
			tx_strm.next_out = (Bytef *) obuf;
			tx_strm.avail_out = AVAIL_OUT_SIZE(CHUNK_SIZE);
			r = deflate(&tx_strm, Z_INSERT_ONLY);
			if (r != Z_OK || tx_strm.avail_in != 0) {
				rprintf(FERROR, "deflate on token returned %d (%d bytes left)\n",
					r, tx_strm.avail_in);
				exit_cleanup(RERR_STREAMIO);
			}
		} while (toklen > 0);
	}
}

/* tells us what the receiver is in the middle of doing */
static enum { r_init, r_idle, r_running, r_inflating, r_inflated } recv_state;

/* for inflating stuff */
static z_stream rx_strm;
static char *cbuf;
static char *dbuf;

/* for decoding runs of tokens */
static int32 rx_token;
static int32 rx_run;

/* Receive a deflated token and inflate it */
static int32 recv_deflated_token(int f, char **data)
{
	static int init_done;
	static int32 saved_flag;
	int32 n, flag;
	int r;

	for (;;) {
		switch (recv_state) {
		case r_init:
			if (!init_done) {
				rx_strm.next_out = NULL;
				rx_strm.zalloc = NULL;
				rx_strm.zfree = NULL;
				if (inflateInit2(&rx_strm, -15) != Z_OK) {
					rprintf(FERROR, "inflate init failed\n");
					exit_cleanup(RERR_PROTOCOL);
				}
				cbuf = new_array(char, MAX_DATA_COUNT);
				dbuf = new_array(char, AVAIL_OUT_SIZE(CHUNK_SIZE));
				init_done = 1;
			} else {
				inflateReset(&rx_strm);
			}
			recv_state = r_idle;
			rx_token = 0;
			break;

		case r_idle:
		case r_inflated:
			if (saved_flag) {
				flag = saved_flag & 0xff;
				saved_flag = 0;
			} else
				flag = read_byte(f);
			if ((flag & 0xC0) == DEFLATED_DATA) {
				n = ((flag & 0x3f) << 8) + read_byte(f);
				read_buf(f, cbuf, n);
				rx_strm.next_in = (Bytef *)cbuf;
				rx_strm.avail_in = n;
				recv_state = r_inflating;
				break;
			}
			if (recv_state == r_inflated) {
				/* check previous inflated stuff ended correctly */
				rx_strm.avail_in = 0;
				rx_strm.next_out = (Bytef *)dbuf;
				rx_strm.avail_out = AVAIL_OUT_SIZE(CHUNK_SIZE);
				r = inflate(&rx_strm, Z_SYNC_FLUSH);
				n = AVAIL_OUT_SIZE(CHUNK_SIZE) - rx_strm.avail_out;
				/*
				 * Z_BUF_ERROR just means no progress was
				 * made, i.e. the decompressor didn't have
				 * any pending output for us.
				 */
				if (r != Z_OK && r != Z_BUF_ERROR) {
					rprintf(FERROR, "inflate flush returned %d (%d bytes)\n",
						r, n);
					exit_cleanup(RERR_STREAMIO);
				}
				if (n != 0 && r != Z_BUF_ERROR) {
					/* have to return some more data and
					   save the flag for later. */
					saved_flag = flag + 0x10000;
					*data = dbuf;
					return n;
				}
				/*
				 * At this point the decompressor should
				 * be expecting to see the 0, 0, ff, ff bytes.
				 */
				if (!inflateSyncPoint(&rx_strm)) {
					rprintf(FERROR, "decompressor lost sync!\n");
					exit_cleanup(RERR_STREAMIO);
				}
				rx_strm.avail_in = 4;
				rx_strm.next_in = (Bytef *)cbuf;
				cbuf[0] = cbuf[1] = 0;
				cbuf[2] = cbuf[3] = (char)0xff;
				inflate(&rx_strm, Z_SYNC_FLUSH);
				recv_state = r_idle;
			}
			if (flag == END_FLAG) {
				/* that's all folks */
				recv_state = r_init;
				return 0;
			}

			/* here we have a token of some kind */
			if (flag & TOKEN_REL) {
				rx_token += flag & 0x3f;
				flag >>= 6;
			} else
				rx_token = read_int(f);
			if (flag & 1) {
				rx_run = read_byte(f);
				rx_run += read_byte(f) << 8;
				recv_state = r_running;
			}
			return -1 - rx_token;

		case r_inflating:
			rx_strm.next_out = (Bytef *)dbuf;
			rx_strm.avail_out = AVAIL_OUT_SIZE(CHUNK_SIZE);
			r = inflate(&rx_strm, Z_NO_FLUSH);
			n = AVAIL_OUT_SIZE(CHUNK_SIZE) - rx_strm.avail_out;
			if (r != Z_OK) {
				rprintf(FERROR, "inflate returned %d (%d bytes)\n", r, n);
				exit_cleanup(RERR_STREAMIO);
			}
			if (rx_strm.avail_in == 0)
				recv_state = r_inflated;
			if (n != 0) {
				*data = dbuf;
				return n;
			}
			break;

		case r_running:
			++rx_token;
			if (--rx_run == 0)
				recv_state = r_idle;
			return -1 - rx_token;
		}
	}
}

/*
 * put the data corresponding to a token that we've just returned
 * from recv_deflated_token into the decompressor's history buffer.
 */
static void see_deflate_token(char *buf, int32 len)
{
	int r;
	int32 blklen;
	unsigned char hdr[5];

	rx_strm.avail_in = 0;
	blklen = 0;
	hdr[0] = 0;
	do {
		if (rx_strm.avail_in == 0 && len != 0) {
			if (blklen == 0) {
				/* Give it a fake stored-block header. */
				rx_strm.next_in = (Bytef *)hdr;
				rx_strm.avail_in = 5;
				blklen = len;
				if (blklen > 0xffff)
					blklen = 0xffff;
				hdr[1] = blklen;
				hdr[2] = blklen >> 8;
				hdr[3] = ~hdr[1];
				hdr[4] = ~hdr[2];
			} else {
				rx_strm.next_in = (Bytef *)buf;
				rx_strm.avail_in = blklen;
				if (protocol_version >= 31) /* Newer protocols avoid a data-duplicating bug */
					buf += blklen;
				len -= blklen;
				blklen = 0;
			}
		}
		rx_strm.next_out = (Bytef *)dbuf;
		rx_strm.avail_out = AVAIL_OUT_SIZE(CHUNK_SIZE);
		r = inflate(&rx_strm, Z_SYNC_FLUSH);
		if (r != Z_OK && r != Z_BUF_ERROR) {
			rprintf(FERROR, "inflate (token) returned %d\n", r);
			exit_cleanup(RERR_STREAMIO);
		}
	} while (len || rx_strm.avail_out == 0);
}

#ifdef SUPPORT_ZSTD

static ZSTD_inBuffer zstd_in_buff;
static ZSTD_outBuffer zstd_out_buff;
static ZSTD_CCtx *zstd_cctx;

static void send_zstd_token(int f, int32 token, struct map_struct *buf, OFF_T offset, int32 nb)
{
	static int comp_init_done, flush_pending;
	ZSTD_EndDirective flush = ZSTD_e_continue;
	int32 n, r;

	/* initialization */
	if (!comp_init_done) {
		zstd_cctx = ZSTD_createCCtx();
		if (!zstd_cctx) {
			rprintf(FERROR, "compression init failed\n");
			exit_cleanup(RERR_PROTOCOL);
		}

		obuf = new_array(char, OBUF_SIZE);

		ZSTD_CCtx_setParameter(zstd_cctx, ZSTD_c_compressionLevel, do_compression_level);
		zstd_out_buff.dst = obuf + 2;

		comp_init_done = 1;
	}

	if (last_token == -1) {
		last_run_end = 0;
		run_start = token;
		flush_pending = 0;
	} else if (last_token == -2) {
		run_start = token;
	} else if (nb != 0 || token != last_token + 1 || token >= run_start + 65536) {
		/* output previous run */
		r = run_start - last_run_end;
		n = last_token - run_start;

		if (r >= 0 && r <= 63) {
			write_byte(f, (n==0? TOKEN_REL: TOKENRUN_REL) + r);
		} else {
			write_byte(f, (n==0? TOKEN_LONG: TOKENRUN_LONG));
			write_int(f, run_start);
		}
		if (n != 0) {
			write_byte(f, n);
			write_byte(f, n >> 8);
		}
		last_run_end = last_token;
		run_start = token;
	}

	last_token = token;

	if (nb || flush_pending) {

		zstd_in_buff.src = map_ptr(buf, offset, nb);
		zstd_in_buff.size = nb;
		zstd_in_buff.pos = 0;

		do {
			if (zstd_out_buff.size == 0) {
				zstd_out_buff.size = MAX_DATA_COUNT;
				zstd_out_buff.pos = 0;
			}

			/* File ended, flush */
			if (token != -2)
				flush = ZSTD_e_flush;

			r = ZSTD_compressStream2(zstd_cctx, &zstd_out_buff, &zstd_in_buff, flush);
			if (ZSTD_isError(r)) {
				rprintf(FERROR, "ZSTD_compressStream returned %d\n", r);
				exit_cleanup(RERR_STREAMIO);
			}

			/*
			 * Nothing is sent if the buffer isn't full so avoid smaller
			 * transfers. If a file is finished then we flush the internal
			 * state and send a smaller buffer so that the remote side can
			 * finish the file.
			 */
			if (zstd_out_buff.pos == zstd_out_buff.size || flush == ZSTD_e_flush) {
				n = zstd_out_buff.pos;

				obuf[0] = DEFLATED_DATA + (n >> 8);
				obuf[1] = n;
				write_buf(f, obuf, n+2);

				zstd_out_buff.size = 0;
			}
			/*
			 * Loop while the input buffer isn't full consumed or the
			 * internal state isn't fully flushed.
			 */
		} while (zstd_in_buff.pos < zstd_in_buff.size || r > 0);
		flush_pending = token == -2;
	}

	if (token == -1) {
		/* end of file - clean up */
		write_byte(f, END_FLAG);
	}
}

static ZSTD_DCtx *zstd_dctx;

static int32 recv_zstd_token(int f, char **data)
{
	static int decomp_init_done;
	static int out_buffer_size;
	int32 n, flag;
	int r;

	if (!decomp_init_done) {
		zstd_dctx = ZSTD_createDCtx();
		if (!zstd_dctx) {
			rprintf(FERROR, "ZSTD_createDStream failed\n");
			exit_cleanup(RERR_PROTOCOL);
		}

		/* Output buffer fits two decompressed blocks */
		out_buffer_size = ZSTD_DStreamOutSize() * 2;
		cbuf = new_array(char, MAX_DATA_COUNT);
		dbuf = new_array(char, out_buffer_size);

		zstd_in_buff.src = cbuf;
		zstd_out_buff.dst = dbuf;

		decomp_init_done = 1;
	}

	for (;;) {
		switch (recv_state) {
		case r_init:
			recv_state = r_idle;
			rx_token = 0;
			break;

		case r_idle:
			flag = read_byte(f);
			if ((flag & 0xC0) == DEFLATED_DATA) {
				n = ((flag & 0x3f) << 8) + read_byte(f);
				read_buf(f, cbuf, n);

				zstd_in_buff.size = n;
				zstd_in_buff.pos = 0;

				recv_state = r_inflating;
				break;
			}

			if (flag == END_FLAG) {
				/* that's all folks */
				recv_state = r_init;
				return 0;
			}
			/* here we have a token of some kind */
			if (flag & TOKEN_REL) {
				rx_token += flag & 0x3f;
				flag >>= 6;
			} else
				rx_token = read_int(f);
			if (flag & 1) {
				rx_run = read_byte(f);
				rx_run += read_byte(f) << 8;
				recv_state = r_running;
			}
			return -1 - rx_token;

		case r_inflated: /* zstd doesn't get into this state */
			break;

		case r_inflating:
			zstd_out_buff.size = out_buffer_size;
			zstd_out_buff.pos = 0;

			r = ZSTD_decompressStream(zstd_dctx, &zstd_out_buff, &zstd_in_buff);
			n = zstd_out_buff.pos;
			if (ZSTD_isError(r)) {
				rprintf(FERROR, "ZSTD decomp returned %d (%d bytes)\n", r, n);
				exit_cleanup(RERR_STREAMIO);
			}

			/*
			 * If the input buffer is fully consumed and the output
			 * buffer is not full then next step is to read more
			 * data.
			 */
			if (zstd_in_buff.size == zstd_in_buff.pos && n < out_buffer_size)
				recv_state = r_idle;

			if (n != 0) {
				*data = dbuf;
				return n;
			}
			break;

		case r_running:
			++rx_token;
			if (--rx_run == 0)
				recv_state = r_idle;
			return -1 - rx_token;
		}
	}
}
#endif /* SUPPORT_ZSTD */

#ifdef SUPPORT_LZ4
static void
send_compressed_token(int f, int32 token, struct map_struct *buf, OFF_T offset, int32 nb)
{
	static int init_done, flush_pending;
	int size = MAX(LZ4_compressBound(CHUNK_SIZE), MAX_DATA_COUNT+2);
	int32 n, r;

	if (last_token == -1) {
		if (!init_done) {
			obuf = new_array(char, size);
			init_done = 1;
		}
		last_run_end = 0;
		run_start = token;
		flush_pending = 0;
	} else if (last_token == -2) {
		run_start = token;
	} else if (nb != 0 || token != last_token + 1 || token >= run_start + 65536) {
		/* output previous run */
		r = run_start - last_run_end;
		n = last_token - run_start;
		if (r >= 0 && r <= 63) {
			write_byte(f, (n==0? TOKEN_REL: TOKENRUN_REL) + r);
		} else {
			write_byte(f, (n==0? TOKEN_LONG: TOKENRUN_LONG));
			write_int(f, run_start);
		}
		if (n != 0) {
			write_byte(f, n);
			write_byte(f, n >> 8);
		}
		last_run_end = last_token;
		run_start = token;
	}

	last_token = token;

	if (nb != 0 || flush_pending) {
		int available_in, available_out = 0;
		const char *next_in;

		do {
			char *next_out = obuf + 2;

			if (available_out == 0) {
				available_in = MIN(nb, MAX_DATA_COUNT);
				next_in = map_ptr(buf, offset, available_in);
			} else
				available_in /= 2;

			available_out = LZ4_compress_default(next_in, next_out, available_in, size - 2);
			if (!available_out) {
				rprintf(FERROR, "compress returned %d\n", available_out);
				exit_cleanup(RERR_STREAMIO);
			}
			if (available_out <= MAX_DATA_COUNT) {
				obuf[0] = DEFLATED_DATA + (available_out >> 8);
				obuf[1] = available_out;

				write_buf(f, obuf, available_out + 2);

				available_out = 0;
				nb -= available_in;
				offset += available_in;
			}
		} while (nb != 0);
		flush_pending = token == -2;
	}
	if (token == -1) {
		/* end of file - clean up */
		write_byte(f, END_FLAG);
	}
}

static int32 recv_compressed_token(int f, char **data)
{
	static int init_done;
	int32 n, flag;
	int size = MAX(LZ4_compressBound(CHUNK_SIZE), MAX_DATA_COUNT+2);
	static const char *next_in;
	static int avail_in;
	int avail_out;

	for (;;) {
		switch (recv_state) {
		case r_init:
			if (!init_done) {
				cbuf = new_array(char, MAX_DATA_COUNT);
				dbuf = new_array(char, size);
				init_done = 1;
			}
			recv_state = r_idle;
			rx_token = 0;
			break;

		case r_idle:
			flag = read_byte(f);
			if ((flag & 0xC0) == DEFLATED_DATA) {
				n = ((flag & 0x3f) << 8) + read_byte(f);
				read_buf(f, cbuf, n);
				next_in = (char *)cbuf;
				avail_in = n;
				recv_state = r_inflating;
				break;
			}

			if (flag == END_FLAG) {
				/* that's all folks */
				recv_state = r_init;
				return 0;
			}

			/* here we have a token of some kind */
			if (flag & TOKEN_REL) {
				rx_token += flag & 0x3f;
				flag >>= 6;
			} else
				rx_token = read_int(f);
			if (flag & 1) {
				rx_run = read_byte(f);
				rx_run += read_byte(f) << 8;
				recv_state = r_running;
			}
			return -1 - rx_token;

		case r_inflating:
			avail_out = LZ4_decompress_safe(next_in, dbuf, avail_in, size);
			if (avail_out < 0) {
				rprintf(FERROR, "uncompress failed: %d\n", avail_out);
				exit_cleanup(RERR_STREAMIO);
			}
			recv_state = r_idle;
			*data = dbuf;
			return avail_out;

		case r_inflated: /* lz4 doesn't get into this state */
			break;

		case r_running:
			++rx_token;
			if (--rx_run == 0)
				recv_state = r_idle;
			return -1 - rx_token;
		}
	}
}
#endif /* SUPPORT_LZ4 */

/**
 * Transmit a verbatim buffer of length @p n followed by a token.
 * If token == -1 then we have reached EOF
 * If n == 0 then don't send a buffer
 */
void send_token(int f, int32 token, struct map_struct *buf, OFF_T offset,
		int32 n, int32 toklen)
{
	switch (do_compression) {
	case CPRES_NONE:
		simple_send_token(f, token, buf, offset, n);
		break;
	case CPRES_ZLIB:
	case CPRES_ZLIBX:
		send_deflated_token(f, token, buf, offset, n, toklen);
		break;
#ifdef SUPPORT_ZSTD
	case CPRES_ZSTD:
		send_zstd_token(f, token, buf, offset, n);
		break;
#endif
#ifdef SUPPORT_LZ4
	case CPRES_LZ4:
		send_compressed_token(f, token, buf, offset, n);
		break;
#endif
	default:
		NOISY_DEATH("Unknown do_compression value");
	}
}

/*
 * receive a token or buffer from the other end. If the return value is >0 then
 * it is a data buffer of that length, and *data will point at the data.
 * if the return value is -i then it represents token i-1
 * if the return value is 0 then the end has been reached
 */
int32 recv_token(int f, char **data)
{
	switch (do_compression) {
	case CPRES_NONE:
		return simple_recv_token(f,data);
	case CPRES_ZLIB:
	case CPRES_ZLIBX:
		return recv_deflated_token(f, data);
#ifdef SUPPORT_ZSTD
	case CPRES_ZSTD:
		return recv_zstd_token(f, data);
#endif
#ifdef SUPPORT_LZ4
	case CPRES_LZ4:
		return recv_compressed_token(f, data);
#endif
	default:
		NOISY_DEATH("Unknown do_compression value");
	}
}

/*
 * look at the data corresponding to a token, if necessary
 */
void see_token(char *data, int32 toklen)
{
	switch (do_compression) {
	case CPRES_NONE:
		break;
	case CPRES_ZLIB:
		see_deflate_token(data, toklen);
		break;
	case CPRES_ZLIBX:
		break;
#ifdef SUPPORT_ZSTD
	case CPRES_ZSTD:
		break;
#endif
#ifdef SUPPORT_LZ4
	case CPRES_LZ4:
		/*see_uncompressed_token(data, toklen);*/
		break;
#endif
	default:
		NOISY_DEATH("Unknown do_compression value");
	}
}
/*
 * Simple utility used only by the test harness.
 *
 * Copyright (C) 2002 Martin Pool
 * Copyright (C) 2003-2020 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"

/* These are to make syscall.o shut up. */
int dry_run = 0;
int am_root = 0;
int am_sender = 1;
int read_only = 1;
int list_only = 0;
int copy_links = 0;
int copy_unsafe_links = 0;

int
main(int argc, char **argv)
{
	int i;

	if (argc <= 1) {
		fprintf(stderr, "trimslash: needs at least one argument\n");
		return 1;
	}

	for (i = 1; i < argc; i++) {
		trim_trailing_slashes(argv[i]);	/* modify in place */
		printf("%s\n", argv[i]);
	}
	return 0;
}
/*
 * Handle the mapping of uid/gid and user/group names between systems.
 *
 * Copyright (C) 1996 Andrew Tridgell
 * Copyright (C) 1996 Paul Mackerras
 * Copyright (C) 2004-2022 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

/* If the source username/group does not exist on the target then use
 * the numeric IDs.  Never do any mapping for uid=0 or gid=0 as these
 * are special. */

#include "rsync.h"
#include "ifuncs.h"
#include "itypes.h"
#include "io.h"

extern int am_root;
extern int preserve_uid;
extern int preserve_gid;
extern int preserve_acls;
extern int numeric_ids;
extern int xmit_id0_names;
extern pid_t namecvt_pid;
extern gid_t our_gid;
extern char *usermap;
extern char *groupmap;

#ifdef HAVE_GETGROUPS
# ifndef GETGROUPS_T
#  define GETGROUPS_T gid_t
# endif
#endif

#define NFLAGS_WILD_NAME_MATCH (1<<0)
#define NFLAGS_NAME_MATCH (1<<1)

union name_or_id {
	const char *name;
	id_t max_id;
};

struct idlist {
	struct idlist *next;
	union name_or_id u;
	id_t id, id2;
	uint16 flags;
};

static struct idlist *uidlist, *uidmap;
static struct idlist *gidlist, *gidmap;

static inline int id_eq_uid(id_t id, uid_t uid)
{
	return id == (id_t)uid;
}

static inline int id_eq_gid(id_t id, gid_t gid)
{
	return id == (id_t)gid;
}

static id_t id_parse(const char *num_str)
{
	id_t tmp, num = 0;
	const char *cp = num_str;

	while (*cp) {
		if (!isDigit(cp)) {
		  invalid_num:
			rprintf(FERROR, "Invalid ID number: %s\n", num_str);
			exit_cleanup(RERR_SYNTAX);
		}
		tmp = num * 10 + *cp++ - '0';
		if (tmp < num)
			goto invalid_num;
		num = tmp;
	}

	return num;
}

static struct idlist *add_to_list(struct idlist **root, id_t id, union name_or_id noiu,
				  id_t id2, uint16 flags)
{
	struct idlist *node = new(struct idlist);
	node->next = *root;
	node->u = noiu;
	node->id = id;
	node->id2 = id2;
	node->flags = flags;
	*root = node;
	return node;
}

/* turn a uid into a user name */
const char *uid_to_user(uid_t uid)
{
	const char *name = NULL;

	if (namecvt_pid) {
		id_t id = uid;
		namecvt_call("uid", &name, &id);
	} else {
		struct passwd *pass = getpwuid(uid);
		if (pass)
			name = strdup(pass->pw_name);
	}

	return name;
}

/* turn a gid into a group name */
const char *gid_to_group(gid_t gid)
{
	const char *name = NULL;

	if (namecvt_pid) {
		id_t id = gid;
		namecvt_call("gid", &name, &id);
	} else {
		struct group *grp = getgrgid(gid);
		if (grp)
			name = strdup(grp->gr_name);
	}

	return name;
}

/* Parse a user name or (optionally) a number into a uid */
int user_to_uid(const char *name, uid_t *uid_p, BOOL num_ok)
{
	if (!name || !*name)
		return 0;

	if (num_ok && name[strspn(name, "0123456789")] == '\0') {
		*uid_p = id_parse(name);
		return 1;
	}

	if (namecvt_pid) {
		id_t id;
		if (!namecvt_call("usr", &name, &id))
			return 0;
		*uid_p = id;
	} else {
		struct passwd *pass = getpwnam(name);
		if (!pass)
			return 0;
		*uid_p = pass->pw_uid;
	}

	return 1;
}

/* Parse a group name or (optionally) a number into a gid */
int group_to_gid(const char *name, gid_t *gid_p, BOOL num_ok)
{
	if (!name || !*name)
		return 0;

	if (num_ok && name[strspn(name, "0123456789")] == '\0') {
		*gid_p = id_parse(name);
		return 1;
	}

	if (namecvt_pid) {
		id_t id;
		if (!namecvt_call("grp", &name, &id))
			return 0;
		*gid_p = id;
	} else {
		struct group *grp = getgrnam(name);
		if (!grp)
			return 0;
		*gid_p = grp->gr_gid;
	}

	return 1;
}

static int is_in_group(gid_t gid)
{
#ifdef HAVE_GETGROUPS
	static gid_t last_in;
	static int ngroups = -2, last_out = -1;
	static GETGROUPS_T *gidset;
	int n;

	if (gid == last_in && last_out >= 0)
		return last_out;
	if (ngroups < -1) {
		if ((ngroups = getgroups(0, NULL)) < 0)
			ngroups = 0;
		gidset = new_array(GETGROUPS_T, ngroups+1);
		if (ngroups > 0)
			ngroups = getgroups(ngroups, gidset);
		/* The default gid might not be in the list on some systems. */
		for (n = 0; n < ngroups; n++) {
			if ((gid_t)gidset[n] == our_gid)
				break;
		}
		if (n == ngroups)
			gidset[ngroups++] = our_gid;
		if (DEBUG_GTE(OWN, 2)) {
			int pos;
			char *gidbuf = new_array(char, ngroups*21+32);
			pos = snprintf(gidbuf, 32, "process has %d gid%s: ", ngroups, ngroups == 1? "" : "s");
			for (n = 0; n < ngroups; n++) {
				pos += snprintf(gidbuf+pos, 21, " %d", (int)gidset[n]);
			}
			rprintf(FINFO, "%s\n", gidbuf);
			free(gidbuf);
		}
	}

	last_in = gid;
	for (n = 0; n < ngroups; n++) {
		if ((gid_t)gidset[n] == gid)
			return last_out = 1;
	}
	return last_out = 0;

#else
	return gid == our_gid;
#endif
}

/* Add a uid/gid to its list of ids.  Only called on receiving side. */
static struct idlist *recv_add_id(struct idlist **idlist_ptr, struct idlist *idmap,
				  id_t id, const char *name)
{
	struct idlist *node;
	union name_or_id noiu;
	int flag;
	id_t id2;

	noiu.name = name; /* ensure that add_to_list() gets the raw value. */
	if (!name)
		name = "";

	for (node = idmap; node; node = node->next) {
		if (node->flags & NFLAGS_WILD_NAME_MATCH) {
			if (!wildmatch(node->u.name, name))
				continue;
		} else if (node->flags & NFLAGS_NAME_MATCH) {
			if (strcmp(node->u.name, name) != 0)
				continue;
		} else if (node->u.max_id) {
			if (id < node->id || id > node->u.max_id)
				continue;
		} else {
			if (node->id != id)
				continue;
		}
		break;
	}
	if (node)
		id2 = node->id2;
	else if (*name && id) {
		if (idlist_ptr == &uidlist) {
			uid_t uid;
			id2 = user_to_uid(name, &uid, False) ? (id_t)uid : id;
		} else {
			gid_t gid;
			id2 = group_to_gid(name, &gid, False) ? (id_t)gid : id;
		}
	} else
		id2 = id;

	flag = idlist_ptr == &gidlist && !am_root && !is_in_group(id2) ? FLAG_SKIP_GROUP : 0;
	node = add_to_list(idlist_ptr, id, noiu, id2, flag);

	if (DEBUG_GTE(OWN, 2)) {
		rprintf(FINFO, "%sid %u(%s) maps to %u\n",
			idlist_ptr == &uidlist ? "u" : "g",
			(unsigned)id, name, (unsigned)id2);
	}

	return node;
}

/* this function is a definite candidate for a faster algorithm */
uid_t match_uid(uid_t uid)
{
	static struct idlist *last = NULL;
	struct idlist *list;

	if (last && id_eq_uid(last->id, uid))
		return last->id2;

	for (list = uidlist; list; list = list->next) {
		if (id_eq_uid(list->id, uid))
			break;
	}

	if (!list)
		list = recv_add_id(&uidlist, uidmap, uid, NULL);
	last = list;

	return list->id2;
}

gid_t match_gid(gid_t gid, uint16 *flags_ptr)
{
	static struct idlist *last = NULL;
	struct idlist *list;

	if (last && id_eq_gid(last->id, gid))
		list = last;
	else {
		for (list = gidlist; list; list = list->next) {
			if (id_eq_gid(list->id, gid))
				break;
		}
		if (!list)
			list = recv_add_id(&gidlist, gidmap, gid, NULL);
		last = list;
	}

	if (flags_ptr && list->flags & FLAG_SKIP_GROUP)
		*flags_ptr |= FLAG_SKIP_GROUP;
	return list->id2;
}

/* Add a uid to the list of uids.  Only called on sending side. */
const char *add_uid(uid_t uid)
{
	struct idlist *list;
	struct idlist *node;
	union name_or_id noiu;

	for (list = uidlist; list; list = list->next) {
		if (id_eq_uid(list->id, uid))
			return NULL;
	}

	noiu.name = uid_to_user(uid);
	node = add_to_list(&uidlist, uid, noiu, 0, 0);
	return node->u.name;
}

/* Add a gid to the list of gids.  Only called on sending side. */
const char *add_gid(gid_t gid)
{
	struct idlist *list;
	struct idlist *node;
	union name_or_id noiu;

	for (list = gidlist; list; list = list->next) {
		if (id_eq_gid(list->id, gid))
			return NULL;
	}

	noiu.name = gid_to_group(gid);
	node = add_to_list(&gidlist, gid, noiu, 0, 0);
	return node->u.name;
}

static void send_one_name(int f, id_t id, const char *name)
{
	int len;

	if (!name)
		name = "";
	if ((len = strlen(name)) > 255) /* Impossible? */
		len = 255;

	write_varint30(f, id);
	write_byte(f, len);
	if (len)
		write_buf(f, name, len);
}

static void send_one_list(int f, struct idlist *idlist, int usernames)
{
	struct idlist *list;

	/* we send sequences of id/byte-len/name */
	for (list = idlist; list; list = list->next) {
		if (list->id && list->u.name)
			send_one_name(f, list->id, list->u.name);
	}

	/* Terminate the uid list with 0 (which was excluded above).
	 * A modern rsync also sends the name of id 0. */
	if (xmit_id0_names)
		send_one_name(f, 0, usernames ? uid_to_user(0) : gid_to_group(0));
	else
		write_varint30(f, 0);
}

/* send a complete uid/gid mapping to the peer */
void send_id_lists(int f)
{
	if (preserve_uid || preserve_acls)
		send_one_list(f, uidlist, 1);

	if (preserve_gid || preserve_acls)
		send_one_list(f, gidlist, 0);
}

uid_t recv_user_name(int f, uid_t uid)
{
	struct idlist *node;
	int len = read_byte(f);
	char *name;

	if (len) {
		name = new_array(char, len+1);
		read_sbuf(f, name, len);
		if (numeric_ids < 0) {
			free(name);
			name = NULL;
		}
	} else
		name = NULL;

	node = recv_add_id(&uidlist, uidmap, uid, name); /* node keeps name's memory */
	return node->id2;
}

gid_t recv_group_name(int f, gid_t gid, uint16 *flags_ptr)
{
	struct idlist *node;
	int len = read_byte(f);
	char *name;

	if (len) {
		name = new_array(char, len+1);
		read_sbuf(f, name, len);
		if (numeric_ids < 0) {
			free(name);
			name = NULL;
		}
	} else
		name = NULL;

	node = recv_add_id(&gidlist, gidmap, gid, name); /* node keeps name's memory */
	if (flags_ptr && node->flags & FLAG_SKIP_GROUP)
		*flags_ptr |= FLAG_SKIP_GROUP;
	return node->id2;
}

/* recv a complete uid/gid mapping from the peer and map the uid/gid
 * in the file list to local names */
void recv_id_list(int f, struct file_list *flist)
{
	id_t id;
	int i;

	if ((preserve_uid || preserve_acls) && numeric_ids <= 0) {
		/* read the uid list */
		while ((id = read_varint30(f)) != 0)
			recv_user_name(f, id);
		if (xmit_id0_names)
			recv_user_name(f, 0);
	}

	if ((preserve_gid || preserve_acls) && numeric_ids <= 0) {
		/* read the gid list */
		while ((id = read_varint30(f)) != 0)
			recv_group_name(f, id, NULL);
		if (xmit_id0_names)
			recv_group_name(f, 0, NULL);
	}

	/* Now convert all the uids/gids from sender values to our values. */
#ifdef SUPPORT_ACLS
	if (preserve_acls && (!numeric_ids || usermap || groupmap))
		match_acl_ids();
#endif
	if (am_root && preserve_uid && (!numeric_ids || usermap)) {
		for (i = 0; i < flist->used; i++)
			F_OWNER(flist->files[i]) = match_uid(F_OWNER(flist->files[i]));
	}
	if (preserve_gid && (!am_root || !numeric_ids || groupmap)) {
		for (i = 0; i < flist->used; i++) {
			F_GROUP(flist->files[i]) = match_gid(F_GROUP(flist->files[i]), &flist->files[i]->flags);
		}
	}
}

void parse_name_map(char *map, BOOL usernames)
{
	struct idlist **idmap_ptr = usernames ? &uidmap : &gidmap;
	struct idlist **idlist_ptr = usernames ? &uidlist : &gidlist;
	char *colon, *cp = map + strlen(map);
	union name_or_id noiu;
	id_t id1;
	uint16 flags;

	/* Parse the list in reverse, so the order in the struct is right. */
	while (1) {
		while (cp > map && cp[-1] != ',') cp--;
		if (!(colon = strchr(cp, ':'))) {
			rprintf(FERROR, "No colon found in --%smap: %s\n",
				usernames ? "user" : "group", cp);
			exit_cleanup(RERR_SYNTAX);
		}
		if (!colon[1]) {
			rprintf(FERROR, "No name found after colon --%smap: %s\n",
				usernames ? "user" : "group", cp);
			exit_cleanup(RERR_SYNTAX);
		}
		*colon = '\0';

		if (isDigit(cp)) {
			char *dash = strchr(cp, '-');
			if (strspn(cp, "0123456789-") != (size_t)(colon - cp)
			 || (dash && (!dash[1] || strchr(dash+1, '-')))) {
				rprintf(FERROR, "Invalid number in --%smap: %s\n",
					usernames ? "user" : "group", cp);
				exit_cleanup(RERR_SYNTAX);
			}
			if (dash) {
				*dash = '\0';
				noiu.max_id = id_parse(dash+1);
			} else
				noiu.max_id = 0;
			flags = 0;
			id1 = id_parse(cp);
			if (dash)
				*dash = '-';
		} else if (strpbrk(cp, "*[?")) {
			flags = NFLAGS_WILD_NAME_MATCH;
			noiu.name = cp;
			id1 = 0;
		} else {
			flags = NFLAGS_NAME_MATCH;
			noiu.name = cp;
			id1 = 0;
		}

		if (usernames) {
			uid_t uid;
			if (user_to_uid(colon+1, &uid, True))
				add_to_list(idmap_ptr, id1, noiu, uid, flags);
			else {
				rprintf(FERROR, "Unknown --usermap name on receiver: %s\n", colon+1);
			}
		} else {
			gid_t gid;
			if (group_to_gid(colon+1, &gid, True))
				add_to_list(idmap_ptr, id1, noiu, gid, flags);
			else {
				rprintf(FERROR, "Unknown --groupmap name on receiver: %s\n", colon+1);
			}
		}

		if (cp == map)
			break;

		*--cp = '\0'; /* replace comma */
	}

	/* If the sender isn't going to xmit the id0 name, we assume it's "root". */
	if (!xmit_id0_names)
		recv_add_id(idlist_ptr, *idmap_ptr, 0, numeric_ids ? NULL : "root");
}

#ifdef HAVE_GETGROUPLIST
const char *getallgroups(uid_t uid, item_list *gid_list)
{
	struct passwd *pw;
	gid_t *gid_array;
	int size;

	if ((pw = getpwuid(uid)) == NULL)
		return "getpwuid failed";

	gid_list->count = 0; /* We're overwriting any items in the list */
	(void)EXPAND_ITEM_LIST(gid_list, gid_t, 32);
	size = gid_list->malloced;

	/* Get all the process's groups, with the pw_gid group first. */
	if (getgrouplist(pw->pw_name, pw->pw_gid, gid_list->items, &size) < 0) {
		if (size > (int)gid_list->malloced) {
			gid_list->count = gid_list->malloced;
			(void)EXPAND_ITEM_LIST(gid_list, gid_t, size);
			if (getgrouplist(pw->pw_name, pw->pw_gid, gid_list->items, &size) < 0)
				size = -1;
		} else
			size = -1;
		if (size < 0)
			return "getgrouplist failed";
	}
	gid_list->count = size;
	gid_array = gid_list->items;

	/* Paranoia: is the default group not first in the list? */
	if (gid_array[0] != pw->pw_gid) {
		int j;
		for (j = 1; j < size; j++) {
			if (gid_array[j] == pw->pw_gid)
				break;
		}
		if (j == size) { /* The default group wasn't found! */
			(void)EXPAND_ITEM_LIST(gid_list, gid_t, size+1);
			gid_array = gid_list->items;
		}
		gid_array[j] = gid_array[0];
		gid_array[0] = pw->pw_gid;
	}

	return NULL;
}
#endif
/*
 * Some usage & version related functions.
 *
 * Copyright (C) 2002-2022 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"
#include "version.h"
#include "latest-year.h"
#include "git-version.h"
#include "default-cvsignore.h"
#include "itypes.h"

extern struct name_num_obj valid_checksums, valid_compressions, valid_auth_checksums;

static char *istring(const char *fmt, int val)
{
	char *str;
	if (asprintf(&str, fmt, val) < 0)
		out_of_memory("istring");
	return str;
}

static void print_info_flags(enum logcode f)
{
	STRUCT_STAT *dumstat;
	BOOL as_json = f == FNONE ? 1 : 0; /* We use 1 == first attribute, 2 == need closing array */
	char line_buf[75], item_buf[32];
	int line_len, j;
	char *info_flags[] = {

	"*Capabilities",

		istring("%d-bit files", (int)(sizeof (OFF_T) * 8)),
		istring("%d-bit inums", (int)(sizeof dumstat->st_ino * 8)), /* Don't check ino_t! */
		istring("%d-bit timestamps", (int)(sizeof (time_t) * 8)),
		istring("%d-bit long ints", (int)(sizeof (int64) * 8)),

#ifndef HAVE_SOCKETPAIR
		"no "
#endif
			"socketpairs",

#ifndef SUPPORT_LINKS
		"no "
#endif
			"symlinks",

#ifndef CAN_SET_SYMLINK_TIMES
		"no "
#endif
			"symtimes",

#ifndef SUPPORT_HARD_LINKS
		"no "
#endif
			"hardlinks",

#ifndef CAN_HARDLINK_SPECIAL
		"no "
#endif
			"hardlink-specials",

#ifndef CAN_HARDLINK_SYMLINK
		"no "
#endif
			"hardlink-symlinks",

#ifndef INET6
		"no "
#endif
			"IPv6",

#ifndef SUPPORT_ATIMES
		"no "
#endif
			"atimes",

		"batchfiles",

#ifndef HAVE_FTRUNCATE
		"no "
#endif
			"inplace",

#ifndef HAVE_FTRUNCATE
		"no "
#endif
			"append",

#ifndef SUPPORT_ACLS
		"no "
#endif
			"ACLs",

#ifndef SUPPORT_XATTRS
		"no "
#endif
			"xattrs",

#ifdef RSYNC_USE_SECLUDED_ARGS
		"default "
#else
		"optional "
#endif
			"secluded-args",

#ifndef ICONV_OPTION
		"no "
#endif
			"iconv",

#ifndef SUPPORT_PREALLOCATION
		"no "
#endif
			"prealloc",

#ifndef HAVE_MKTIME
		"no "
#endif
			"stop-at",

#ifndef SUPPORT_CRTIMES
		"no "
#endif
			"crtimes",

	"*Optimizations",

#ifndef USE_ROLL_SIMD
		"no "
#endif
			"SIMD-roll",

#ifndef USE_ROLL_ASM
		"no "
#endif
			"asm-roll",

#ifndef USE_OPENSSL
		"no "
#endif
			"openssl-crypto",

#ifndef USE_MD5_ASM
		"no "
#endif
			"asm-MD5",

		NULL
	};

	for (line_len = 0, j = 0; ; j++) {
		char *str = info_flags[j], *next_nfo = str ? info_flags[j+1] : NULL;
		int need_comma = next_nfo && *next_nfo != '*' ? 1 : 0;
		int item_len;
		if (!str || *str == '*')
			item_len = 1000;
		else if (as_json) {
			char *space = strchr(str, ' ');
			int is_no = space && strncmp(str, "no ", 3) == 0;
			int is_bits = space && isDigit(str);
			char *quot = space && !is_no && !is_bits ? "\"" : "";
			char *item = space ? space + 1 : str;
			char *val = !space ? "true" : is_no ? "false" : str;
			int val_len = !space ? 4 : is_no ? 5 : space - str;
			if (is_bits && (space = strchr(val, '-')) != NULL)
			    val_len = space - str;
			item_len = snprintf(item_buf, sizeof item_buf,
					   " \"%s%s\": %s%.*s%s%s", item, is_bits ? "bits" : "",
					   quot, val_len, val, quot, need_comma ? "," : "");
			if (is_bits)
				item_buf[strlen(item)+2-1] = '_'; /* Turn the 's' into a '_' */
			for (space = item; (space = strpbrk(space, " -")) != NULL; space++)
				item_buf[space - item + 2] = '_';
		} else
			item_len = snprintf(item_buf, sizeof item_buf, " %s%s", str, need_comma ? "," : "");
		if (line_len && line_len + item_len >= (int)sizeof line_buf) {
			if (as_json)
				printf("   %s\n", line_buf);
			else
				rprintf(f, "   %s\n", line_buf);
			line_len = 0;
		}
		if (!str)
			break;
		if (*str == '*') {
			if (as_json) {
				if (as_json == 2)
					printf("  }");
				else
					as_json = 2;
				printf(",\n  \"%c%s\": {\n", toLower(str+1), str+2);
			} else
				rprintf(f, "%s:\n", str+1);
		} else {
			strlcpy(line_buf + line_len, item_buf, sizeof line_buf - line_len);
			line_len += item_len;
		}
	}
	if (as_json == 2)
		printf("  }");
}

static void output_nno_list(enum logcode f, const char *name, struct name_num_obj *nno)
{
	char namebuf[64], tmpbuf[256];
	char *tok, *next_tok, *comma = ",";
	char *cp;

	/* Using '(' ensures that we get a trailing "none" but also includes aliases. */
	get_default_nno_list(nno, tmpbuf, sizeof tmpbuf - 1, '(');
	if (f != FNONE) {
		rprintf(f, "%s:\n", name);
		rprintf(f, "    %s\n", tmpbuf);
		return;
	}

	strlcpy(namebuf, name, sizeof namebuf);
	for (cp = namebuf; *cp; cp++) {
		if (*cp == ' ')
			*cp = '_';
		else if (isUpper(cp))
			*cp = toLower(cp);
	}

	printf(",\n  \"%s\": [\n   ", namebuf);

	for (tok = strtok(tmpbuf, " "); tok; tok = next_tok) {
		next_tok = strtok(NULL, " ");
		if (*tok != '(') /* Ignore the alises in the JSON output */
			printf(" \"%s\"%s", tok, comma + (next_tok ? 0 : 1));
	}

	printf("\n  ]");
}

/* A request of f == FNONE wants json on stdout. */
void print_rsync_version(enum logcode f)
{
	char copyright[] = "(C) 1996-" LATEST_YEAR " by Andrew Tridgell, Wayne Davison, and others.";
	char url[] = "https://rsync.samba.org/";
	BOOL first_line = 1;

#define json_line(name, value) \
	do { \
		printf("%c\n  \"%s\": \"%s\"", first_line ? '{' : ',', name, value); \
		first_line = 0; \
	} while (0)

	if (f == FNONE) {
		char verbuf[32];
		json_line("program", RSYNC_NAME);
		json_line("version", rsync_version());
		(void)snprintf(verbuf, sizeof verbuf, "%d.%d", PROTOCOL_VERSION, SUBPROTOCOL_VERSION);
		json_line("protocol", verbuf);
		json_line("copyright", copyright);
		json_line("url", url);
	} else {
#if SUBPROTOCOL_VERSION != 0
		char *subprotocol = istring(".PR%d", SUBPROTOCOL_VERSION);
#else
		char *subprotocol = "";
#endif
		rprintf(f, "%s  version %s  protocol version %d%s\n",
			RSYNC_NAME, rsync_version(), PROTOCOL_VERSION, subprotocol);
		rprintf(f, "Copyright %s\n", copyright);
		rprintf(f, "Web site: %s\n", url);
	}

	print_info_flags(f);

	init_checksum_choices();

	output_nno_list(f, "Checksum list", &valid_checksums);
	output_nno_list(f, "Compress list", &valid_compressions);
	output_nno_list(f, "Daemon auth list", &valid_auth_checksums);

	if (f == FNONE) {
		json_line("license", "GPLv3");
		json_line("caveat", "rsync comes with ABSOLUTELY NO WARRANTY");
		printf("\n}\n");
		fflush(stdout);
		return;
	}

#ifdef MAINTAINER_MODE
	rprintf(f, "Panic Action: \"%s\"\n", get_panic_action());
#endif

#if SIZEOF_INT64 < 8
	rprintf(f, "WARNING: no 64-bit integers on this platform!\n");
#endif
	if (sizeof (int64) != SIZEOF_INT64) {
		rprintf(f,
			"WARNING: size mismatch in SIZEOF_INT64 define (%d != %d)\n",
			(int) SIZEOF_INT64, (int) sizeof (int64));
	}

	rprintf(f,"\n");
	rprintf(f,"rsync comes with ABSOLUTELY NO WARRANTY.  This is free software, and you\n");
	rprintf(f,"are welcome to redistribute it under certain conditions.  See the GNU\n");
	rprintf(f,"General Public Licence for details.\n");
}

void usage(enum logcode F)
{
  print_rsync_version(F);

  rprintf(F,"\n");
  rprintf(F,"rsync is a file transfer program capable of efficient remote update\n");
  rprintf(F,"via a fast differencing algorithm.\n");

  rprintf(F,"\n");
  rprintf(F,"Usage: rsync [OPTION]... SRC [SRC]... DEST\n");
  rprintf(F,"  or   rsync [OPTION]... SRC [SRC]... [USER@]HOST:DEST\n");
  rprintf(F,"  or   rsync [OPTION]... SRC [SRC]... [USER@]HOST::DEST\n");
  rprintf(F,"  or   rsync [OPTION]... SRC [SRC]... rsync://[USER@]HOST[:PORT]/DEST\n");
  rprintf(F,"  or   rsync [OPTION]... [USER@]HOST:SRC [DEST]\n");
  rprintf(F,"  or   rsync [OPTION]... [USER@]HOST::SRC [DEST]\n");
  rprintf(F,"  or   rsync [OPTION]... rsync://[USER@]HOST[:PORT]/SRC [DEST]\n");
  rprintf(F,"The ':' usages connect via remote shell, while '::' & 'rsync://' usages connect\n");
  rprintf(F,"to an rsync daemon, and require SRC or DEST to start with a module name.\n");
  rprintf(F,"\n");
  rprintf(F,"Options\n");
#include "help-rsync.h"
  rprintf(F,"\n");
  rprintf(F,"Use \"rsync --daemon --help\" to see the daemon-mode command-line options.\n");
  rprintf(F,"Please see the rsync(1) and rsyncd.conf(5) manpages for full documentation.\n");
  rprintf(F,"See https://rsync.samba.org/ for updates, bug reports, and answers\n");
}

void daemon_usage(enum logcode F)
{
  print_rsync_version(F);

  rprintf(F,"\n");
  rprintf(F,"Usage: rsync --daemon [OPTION]...\n");
#include "help-rsyncd.h"
  rprintf(F,"\n");
  rprintf(F,"If you were not trying to invoke rsync as a daemon, avoid using any of the\n");
  rprintf(F,"daemon-specific rsync options.  See also the rsyncd.conf(5) manpage.\n");
}

const char *rsync_version(void)
{
	char *ver;
#ifdef RSYNC_GITVER
	ver = RSYNC_GITVER;
#else
	ver = RSYNC_VERSION;
#endif
	return *ver == 'v' ? ver+1 : ver;
}

const char *default_cvsignore(void)
{
	return DEFAULT_CVSIGNORE;
}
/*
 * Utility routines used in rsync.
 *
 * Copyright (C) 1996-2000 Andrew Tridgell
 * Copyright (C) 1996 Paul Mackerras
 * Copyright (C) 2001, 2002 Martin Pool <mbp@samba.org>
 * Copyright (C) 2003-2022 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"
#include "ifuncs.h"
#include "itypes.h"
#include "inums.h"

extern int dry_run;
extern int module_id;
extern int do_fsync;
extern int protect_args;
extern int modify_window;
extern int relative_paths;
extern int preserve_xattrs;
extern int omit_link_times;
extern int preallocate_files;
extern char *module_dir;
extern unsigned int module_dirlen;
extern char *partial_dir;
extern filter_rule_list daemon_filter_list;

int sanitize_paths = 0;

char curr_dir[MAXPATHLEN];
unsigned int curr_dir_len;
int curr_dir_depth; /* This is only set for a sanitizing daemon. */

/* Set a fd into nonblocking mode. */
void set_nonblocking(int fd)
{
	int val;

	if ((val = fcntl(fd, F_GETFL)) == -1)
		return;
	if (!(val & NONBLOCK_FLAG)) {
		val |= NONBLOCK_FLAG;
		fcntl(fd, F_SETFL, val);
	}
}

/* Set a fd into blocking mode. */
void set_blocking(int fd)
{
	int val;

	if ((val = fcntl(fd, F_GETFL)) == -1)
		return;
	if (val & NONBLOCK_FLAG) {
		val &= ~NONBLOCK_FLAG;
		fcntl(fd, F_SETFL, val);
	}
}

/**
 * Create a file descriptor pair - like pipe() but use socketpair if
 * possible (because of blocking issues on pipes).
 *
 * Always set non-blocking.
 */
int fd_pair(int fd[2])
{
	int ret;

#ifdef HAVE_SOCKETPAIR
	ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fd);
#else
	ret = pipe(fd);
#endif

	if (ret == 0) {
		set_nonblocking(fd[0]);
		set_nonblocking(fd[1]);
	}

	return ret;
}

void print_child_argv(const char *prefix, char **cmd)
{
	int cnt = 0;
	rprintf(FCLIENT, "%s ", prefix);
	for (; *cmd; cmd++) {
		/* Look for characters that ought to be quoted.  This
		* is not a great quoting algorithm, but it's
		* sufficient for a log message. */
		if (strspn(*cmd, "abcdefghijklmnopqrstuvwxyz"
			   "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
			   "0123456789"
			   ",.-_=+@/") != strlen(*cmd)) {
			rprintf(FCLIENT, "\"%s\" ", *cmd);
		} else {
			rprintf(FCLIENT, "%s ", *cmd);
		}
		cnt++;
	}
	rprintf(FCLIENT, " (%d args)\n", cnt);
}

/* This returns 0 for success, 1 for a symlink if symlink time-setting
 * is not possible, or -1 for any other error. */
int set_times(const char *fname, STRUCT_STAT *stp)
{
	static int switch_step = 0;

	if (DEBUG_GTE(TIME, 1)) {
		rprintf(FINFO,
			"set modtime, atime of %s to (%ld) %s, (%ld) %s\n",
			fname, (long)stp->st_mtime,
			timestring(stp->st_mtime), (long)stp->st_atime, timestring(stp->st_atime));
	}

	switch (switch_step) {
#ifdef HAVE_SETATTRLIST
#include "case_N.h"
		if (do_setattrlist_times(fname, stp) == 0)
			break;
		if (errno != ENOSYS)
			return -1;
		switch_step++;
#endif

#ifdef HAVE_UTIMENSAT
#include "case_N.h"
		if (do_utimensat(fname, stp) == 0)
			break;
		if (errno != ENOSYS)
			return -1;
		switch_step++;
#endif

#ifdef HAVE_LUTIMES
#include "case_N.h"
		if (do_lutimes(fname, stp) == 0)
			break;
		if (errno != ENOSYS)
			return -1;
		switch_step++;
#endif

#include "case_N.h"
		switch_step++;
		if (!omit_link_times) {
			omit_link_times = 1;
			if (S_ISLNK(stp->st_mode))
				return 1;
		}

#include "case_N.h"
#ifdef HAVE_UTIMES
		if (do_utimes(fname, stp) == 0)
			break;
#else
		if (do_utime(fname, stp) == 0)
			break;
#endif

		return -1;
	}

	return 0;
}

/* Create any necessary directories in fname.  Any missing directories are
 * created with default permissions.  Returns < 0 on error, or the number
 * of directories created. */
int make_path(char *fname, int flags)
{
	char *end, *p;
	int ret = 0;

	if (flags & MKP_SKIP_SLASH) {
		while (*fname == '/')
			fname++;
	}

	while (*fname == '.' && fname[1] == '/')
		fname += 2;

	if (flags & MKP_DROP_NAME) {
		end = strrchr(fname, '/');
		if (!end || end == fname)
			return 0;
		*end = '\0';
	} else
		end = fname + strlen(fname);

	/* Try to find an existing dir, starting from the deepest dir. */
	for (p = end; ; ) {
		if (dry_run) {
			STRUCT_STAT st;
			if (do_stat(fname, &st) == 0) {
				if (S_ISDIR(st.st_mode))
					errno = EEXIST;
				else
					errno = ENOTDIR;
			}
		} else if (do_mkdir(fname, ACCESSPERMS) == 0) {
			ret++;
			break;
		}

		if (errno != ENOENT) {
			STRUCT_STAT st;
			if (errno != EEXIST || (do_stat(fname, &st) == 0 && !S_ISDIR(st.st_mode)))
				ret = -ret - 1;
			break;
		}
		while (1) {
			if (p == fname) {
				/* We got a relative path that doesn't exist, so assume that '.'
				 * is there and just break out and create the whole thing. */
				p = NULL;
				goto double_break;
			}
			if (*--p == '/') {
				if (p == fname) {
					/* We reached the "/" dir, which we assume is there. */
					goto double_break;
				}
				*p = '\0';
				break;
			}
		}
	}
  double_break:

	/* Make all the dirs that we didn't find on the way here. */
	while (p != end) {
		if (p)
			*p = '/';
		else
			p = fname;
		p += strlen(p);
		if (ret < 0) /* Skip mkdir on error, but keep restoring the path. */
			continue;
		if (do_mkdir(fname, ACCESSPERMS) < 0)
			ret = -ret - 1;
		else
			ret++;
	}

	if (flags & MKP_DROP_NAME)
		*end = '/';

	return ret;
}

/**
 * Write @p len bytes at @p ptr to descriptor @p desc, retrying if
 * interrupted.
 *
 * @retval len upon success
 *
 * @retval <0 write's (negative) error code
 *
 * Derived from GNU C's cccp.c.
 */
int full_write(int desc, const char *ptr, size_t len)
{
	int total_written;

	total_written = 0;
	while (len > 0) {
		int written = write(desc, ptr, len);
		if (written < 0)  {
			if (errno == EINTR)
				continue;
			return written;
		}
		total_written += written;
		ptr += written;
		len -= written;
	}
	return total_written;
}

/**
 * Read @p len bytes at @p ptr from descriptor @p desc, retrying if
 * interrupted.
 *
 * @retval >0 the actual number of bytes read
 *
 * @retval 0 for EOF
 *
 * @retval <0 for an error.
 *
 * Derived from GNU C's cccp.c. */
static int safe_read(int desc, char *ptr, size_t len)
{
	int n_chars;

	if (len == 0)
		return len;

	do {
		n_chars = read(desc, ptr, len);
	} while (n_chars < 0 && errno == EINTR);

	return n_chars;
}

/* Remove existing file @dest and reopen, creating a new file with @mode */
static int unlink_and_reopen(const char *dest, mode_t mode)
{
	int ofd;

	if (robust_unlink(dest) && errno != ENOENT) {
		int save_errno = errno;
		rsyserr(FERROR_XFER, errno, "unlink %s", full_fname(dest));
		errno = save_errno;
		return -1;
	}

#ifdef SUPPORT_XATTRS
	if (preserve_xattrs)
		mode |= S_IWUSR;
#endif
	mode &= INITACCESSPERMS;
	if ((ofd = do_open(dest, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode)) < 0) {
		int save_errno = errno;
		rsyserr(FERROR_XFER, save_errno, "open %s", full_fname(dest));
		errno = save_errno;
		return -1;
	}
	return ofd;
}

/* Copy contents of file @source to file @dest with mode @mode.
 *
 * If @tmpfilefd is < 0, copy_file unlinks @dest and then opens a new
 * file with name @dest.
 *
 * Otherwise, copy_file writes to and closes the provided file
 * descriptor.
 *
 * In either case, if --xattrs are being preserved, the dest file will
 * have its xattrs set from the source file.
 *
 * This is used in conjunction with the --temp-dir, --backup, and
 * --copy-dest options. */
int copy_file(const char *source, const char *dest, int tmpfilefd, mode_t mode)
{
	int ifd, ofd;
	char buf[1024 * 8];
	int len;   /* Number of bytes read into `buf'. */
	OFF_T prealloc_len = 0, offset = 0;

	if ((ifd = do_open_nofollow(source, O_RDONLY)) < 0) {
		int save_errno = errno;
		rsyserr(FERROR_XFER, errno, "open %s", full_fname(source));
		errno = save_errno;
		return -1;
	}

	if (tmpfilefd >= 0) {
		ofd = tmpfilefd;
	} else {
		ofd = unlink_and_reopen(dest, mode);
		if (ofd < 0) {
			int save_errno = errno;
			close(ifd);
			errno = save_errno;
			return -1;
		}
	}

#ifdef SUPPORT_PREALLOCATION
	if (preallocate_files) {
		STRUCT_STAT srcst;

		/* Try to preallocate enough space for file's eventual length.  Can
		 * reduce fragmentation on filesystems like ext4, xfs, and NTFS. */
		if (do_fstat(ifd, &srcst) < 0)
			rsyserr(FWARNING, errno, "fstat %s", full_fname(source));
		else if (srcst.st_size > 0) {
			prealloc_len = do_fallocate(ofd, 0, srcst.st_size);
			if (prealloc_len < 0)
				rsyserr(FWARNING, errno, "do_fallocate %s", full_fname(dest));
		}
	}
#endif

	while ((len = safe_read(ifd, buf, sizeof buf)) > 0) {
		if (full_write(ofd, buf, len) < 0) {
			int save_errno = errno;
			rsyserr(FERROR_XFER, errno, "write %s", full_fname(dest));
			close(ifd);
			close(ofd);
			errno = save_errno;
			return -1;
		}
		offset += len;
	}

	if (len < 0) {
		int save_errno = errno;
		rsyserr(FERROR_XFER, errno, "read %s", full_fname(source));
		close(ifd);
		close(ofd);
		errno = save_errno;
		return -1;
	}

	if (close(ifd) < 0) {
		rsyserr(FWARNING, errno, "close failed on %s",
			full_fname(source));
	}

	/* Source file might have shrunk since we fstatted it.
	 * Cut off any extra preallocated zeros from dest file. */
	if (offset < prealloc_len) {
#ifdef HAVE_FTRUNCATE
		/* If we fail to truncate, the dest file may be wrong, so we
		 * must trigger the "partial transfer" error. */
		if (do_ftruncate(ofd, offset) < 0)
			rsyserr(FERROR_XFER, errno, "ftruncate %s", full_fname(dest));
#else
		rprintf(FERROR_XFER, "no ftruncate for over-long pre-alloc: %s", full_fname(dest));
#endif
	}

	if (do_fsync && fsync(ofd) < 0) {
		int save_errno = errno;
		rsyserr(FERROR, errno, "fsync failed on %s", full_fname(dest));
		close(ofd);
		errno = save_errno;
		return -1;
	}

	if (close(ofd) < 0) {
		int save_errno = errno;
		rsyserr(FERROR_XFER, errno, "close failed on %s", full_fname(dest));
		errno = save_errno;
		return -1;
	}

#ifdef SUPPORT_XATTRS
	if (preserve_xattrs)
		copy_xattrs(source, dest);
#endif

	return 0;
}

/* MAX_RENAMES should be 10**MAX_RENAMES_DIGITS */
#define MAX_RENAMES_DIGITS 3
#define MAX_RENAMES 1000

/**
 * Robust unlink: some OS'es (HPUX) refuse to unlink busy files, so
 * rename to <path>/.rsyncNNN instead.
 *
 * Note that successive rsync runs will shuffle the filenames around a
 * bit as long as the file is still busy; this is because this function
 * does not know if the unlink call is due to a new file coming in, or
 * --delete trying to remove old .rsyncNNN files, hence it renames it
 * each time.
 **/
int robust_unlink(const char *fname)
{
#ifndef ETXTBSY
	return do_unlink(fname);
#else
	static int counter = 1;
	int rc, pos, start;
	char path[MAXPATHLEN];

	rc = do_unlink(fname);
	if (rc == 0 || errno != ETXTBSY)
		return rc;

	if ((pos = strlcpy(path, fname, MAXPATHLEN)) >= MAXPATHLEN)
		pos = MAXPATHLEN - 1;

	while (pos > 0 && path[pos-1] != '/')
		pos--;
	pos += strlcpy(path+pos, ".rsync", MAXPATHLEN-pos);

	if (pos > (MAXPATHLEN-MAX_RENAMES_DIGITS-1)) {
		errno = ETXTBSY;
		return -1;
	}

	/* start where the last one left off to reduce chance of clashes */
	start = counter;
	do {
		snprintf(&path[pos], MAX_RENAMES_DIGITS+1, "%03d", counter);
		if (++counter >= MAX_RENAMES)
			counter = 1;
	} while ((rc = access(path, 0)) == 0 && counter != start);

	if (INFO_GTE(MISC, 1)) {
		rprintf(FWARNING, "renaming %s to %s because of text busy\n",
			fname, path);
	}

	/* maybe we should return rename()'s exit status? Nah. */
	if (do_rename(fname, path) != 0) {
		errno = ETXTBSY;
		return -1;
	}
	return 0;
#endif
}

/* Returns 0 on successful rename, 1 if we successfully copied the file
 * across filesystems, -2 if copy_file() failed, and -1 on other errors.
 * If partialptr is not NULL and we need to do a copy, copy the file into
 * the active partial-dir instead of over the destination file. */
int robust_rename(const char *from, const char *to, const char *partialptr,
		  int mode)
{
	int tries = 4;

	/* A resumed in-place partial-dir transfer might call us with from and
	 * to pointing to the same buf if the transfer failed yet again. */
	if (from == to)
		return 0;

	while (tries--) {
		if (do_rename(from, to) == 0)
			return 0;

		switch (errno) {
#ifdef ETXTBSY
		case ETXTBSY:
			if (robust_unlink(to) != 0) {
				errno = ETXTBSY;
				return -1;
			}
			errno = ETXTBSY;
			break;
#endif
		case EXDEV:
			if (partialptr) {
				if (!handle_partial_dir(partialptr,PDIR_CREATE))
					return -2;
				to = partialptr;
			}
			if (copy_file(from, to, -1, mode) != 0)
				return -2;
			do_unlink(from);
			return 1;
		default:
			return -1;
		}
	}
	return -1;
}

static pid_t all_pids[10];
static int num_pids;

/** Fork and record the pid of the child. **/
pid_t do_fork(void)
{
	pid_t newpid = fork();

	if (newpid != 0  &&  newpid != -1) {
		all_pids[num_pids++] = newpid;
	}
	return newpid;
}

/**
 * Kill all children.
 *
 * @todo It would be kind of nice to make sure that they are actually
 * all our children before we kill them, because their pids may have
 * been recycled by some other process.  Perhaps when we wait for a
 * child, we should remove it from this array.  Alternatively we could
 * perhaps use process groups, but I think that would not work on
 * ancient Unix versions that don't support them.
 **/
void kill_all(int sig)
{
	int i;

	for (i = 0; i < num_pids; i++) {
		/* Let's just be a little careful where we
		 * point that gun, hey?  See kill(2) for the
		 * magic caused by negative values. */
		pid_t p = all_pids[i];

		if (p == getpid())
			continue;
		if (p <= 0)
			continue;

		kill(p, sig);
	}
}

/** Lock a byte range in a open file */
int lock_range(int fd, int offset, int len)
{
	struct flock lock;

	lock.l_type = F_WRLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start = offset;
	lock.l_len = len;
	lock.l_pid = 0;

	return fcntl(fd,F_SETLK,&lock) == 0;
}

#define ENSURE_MEMSPACE(buf, type, sz, req) \
	do { if ((req) > sz) buf = realloc_array(buf, type, sz = MAX(sz * 2, req)); } while(0)

static inline void call_glob_match(const char *name, int len, int from_glob,
				   char *arg, int abpos, int fbpos);

static struct glob_data {
	char *arg_buf, *filt_buf, **argv;
	int absize, fbsize, maxargs, argc;
} glob;

static void glob_match(char *arg, int abpos, int fbpos)
{
	int len;
	char *slash;

	while (*arg == '.' && arg[1] == '/') {
		if (fbpos < 0) {
			ENSURE_MEMSPACE(glob.filt_buf, char, glob.fbsize, glob.absize);
			memcpy(glob.filt_buf, glob.arg_buf, abpos + 1);
			fbpos = abpos;
		}
		ENSURE_MEMSPACE(glob.arg_buf, char, glob.absize, abpos + 3);
		glob.arg_buf[abpos++] = *arg++;
		glob.arg_buf[abpos++] = *arg++;
		glob.arg_buf[abpos] = '\0';
	}
	if ((slash = strchr(arg, '/')) != NULL) {
		*slash = '\0';
		len = slash - arg;
	} else
		len = strlen(arg);
	if (strpbrk(arg, "*?[")) {
		struct dirent *di;
		DIR *d;

		if (!(d = opendir(abpos ? glob.arg_buf : ".")))
			return;
		while ((di = readdir(d)) != NULL) {
			char *dname = d_name(di);
			if (dname[0] == '.' && (dname[1] == '\0'
			  || (dname[1] == '.' && dname[2] == '\0')))
				continue;
			if (!wildmatch(arg, dname))
				continue;
			call_glob_match(dname, strlen(dname), 1,
					slash ? arg + len + 1 : NULL,
					abpos, fbpos);
		}
		closedir(d);
	} else {
		call_glob_match(arg, len, 0,
				slash ? arg + len + 1 : NULL,
				abpos, fbpos);
	}
	if (slash)
		*slash = '/';
}

static inline void call_glob_match(const char *name, int len, int from_glob,
				   char *arg, int abpos, int fbpos)
{
	char *use_buf;

	ENSURE_MEMSPACE(glob.arg_buf, char, glob.absize, abpos + len + 2);
	memcpy(glob.arg_buf + abpos, name, len);
	abpos += len;
	glob.arg_buf[abpos] = '\0';

	if (fbpos >= 0) {
		ENSURE_MEMSPACE(glob.filt_buf, char, glob.fbsize, fbpos + len + 2);
		memcpy(glob.filt_buf + fbpos, name, len);
		fbpos += len;
		glob.filt_buf[fbpos] = '\0';
		use_buf = glob.filt_buf;
	} else
		use_buf = glob.arg_buf;

	if (from_glob || (arg && len)) {
		STRUCT_STAT st;
		int is_dir;

		if (do_stat(glob.arg_buf, &st) != 0)
			return;
		is_dir = S_ISDIR(st.st_mode) != 0;
		if (arg && !is_dir)
			return;

		if (daemon_filter_list.head
		 && check_filter(&daemon_filter_list, FLOG, use_buf, is_dir) < 0)
			return;
	}

	if (arg) {
		glob.arg_buf[abpos++] = '/';
		glob.arg_buf[abpos] = '\0';
		if (fbpos >= 0) {
			glob.filt_buf[fbpos++] = '/';
			glob.filt_buf[fbpos] = '\0';
		}
		glob_match(arg, abpos, fbpos);
	} else {
		ENSURE_MEMSPACE(glob.argv, char *, glob.maxargs, glob.argc + 1);
		glob.argv[glob.argc++] = strdup(glob.arg_buf);
	}
}

/* This routine performs wild-card expansion of the pathname in "arg".  Any
 * daemon-excluded files/dirs will not be matched by the wildcards.  Returns 0
 * if a wild-card string is the only returned item (due to matching nothing). */
int glob_expand(const char *arg, char ***argv_p, int *argc_p, int *maxargs_p)
{
	int ret, save_argc;
	char *s;

	if (!arg) {
		if (glob.filt_buf)
			free(glob.filt_buf);
		free(glob.arg_buf);
		memset(&glob, 0, sizeof glob);
		return -1;
	}

	if (sanitize_paths)
		s = sanitize_path(NULL, arg, "", 0, SP_KEEP_DOT_DIRS);
	else {
		s = strdup(arg);
		clean_fname(s, CFN_KEEP_DOT_DIRS | CFN_KEEP_TRAILING_SLASH | CFN_COLLAPSE_DOT_DOT_DIRS);
	}

	ENSURE_MEMSPACE(glob.arg_buf, char, glob.absize, MAXPATHLEN);
	*glob.arg_buf = '\0';

	glob.argc = save_argc = *argc_p;
	glob.argv = *argv_p;
	glob.maxargs = *maxargs_p;

	ENSURE_MEMSPACE(glob.argv, char *, glob.maxargs, 100);

	glob_match(s, 0, -1);

	/* The arg didn't match anything, so add the failed arg to the list. */
	if (glob.argc == save_argc) {
		ENSURE_MEMSPACE(glob.argv, char *, glob.maxargs, glob.argc + 1);
		glob.argv[glob.argc++] = s;
		ret = 0;
	} else {
		free(s);
		ret = 1;
	}

	*maxargs_p = glob.maxargs;
	*argv_p = glob.argv;
	*argc_p = glob.argc;

	return ret;
}

/* This routine is only used in daemon mode. */
void glob_expand_module(char *base1, char *arg, char ***argv_p, int *argc_p, int *maxargs_p)
{
	char *p, *s;
	char *base = base1;
	int base_len = strlen(base);

	if (!arg || !*arg)
		return;

	if (strncmp(arg, base, base_len) == 0)
		arg += base_len;

	if (protect_args) {
		glob_expand(arg, argv_p, argc_p, maxargs_p);
		return;
	}

	arg = strdup(arg);

	if (asprintf(&base," %s/", base1) < 0)
		out_of_memory("glob_expand_module");
	base_len++;

	for (s = arg; *s; s = p + base_len) {
		if ((p = strstr(s, base)) != NULL)
			*p = '\0'; /* split it at this point */
		glob_expand(s, argv_p, argc_p, maxargs_p);
		if (!p)
			break;
	}

	free(arg);
	free(base);
}

/**
 * Convert a string to lower case
 **/
void strlower(char *s)
{
	while (*s) {
		if (isUpper(s))
			*s = toLower(s);
		s++;
	}
}

/**
 * Split a string into tokens based (usually) on whitespace & commas.  If the
 * string starts with a comma (after skipping any leading whitespace), then
 * splitting is done only on commas. No empty tokens are ever returned. */
char *conf_strtok(char *str)
{
	static int commas_only = 0;

	if (str) {
		while (isSpace(str)) str++;
		if (*str == ',') {
			commas_only = 1;
			str++;
		} else
			commas_only = 0;
	}

	while (commas_only) {
		char *end, *tok = strtok(str, ",");
		if (!tok)
			return NULL;
		/* Trim just leading and trailing whitespace. */
		while (isSpace(tok))
			tok++;
		end = tok + strlen(tok);
		while (end > tok && isSpace(end-1))
			*--end = '\0';
		if (*tok)
			return tok;
		str = NULL;
	}

	return strtok(str, " ,\t\r\n");
}

/* Join strings p1 & p2 into "dest" with a guaranteed '/' between them.  (If
 * p1 ends with a '/', no extra '/' is inserted.)  Returns the length of both
 * strings + 1 (if '/' was inserted), regardless of whether the null-terminated
 * string fits into destsize. */
size_t pathjoin(char *dest, size_t destsize, const char *p1, const char *p2)
{
	size_t len = strlcpy(dest, p1, destsize);
	if (len < destsize - 1) {
		if (!len || dest[len-1] != '/')
			dest[len++] = '/';
		if (len < destsize - 1)
			len += strlcpy(dest + len, p2, destsize - len);
		else {
			dest[len] = '\0';
			len += strlen(p2);
		}
	}
	else
		len += strlen(p2) + 1; /* Assume we'd insert a '/'. */
	return len;
}

/* Join any number of strings together, putting them in "dest".  The return
 * value is the length of all the strings, regardless of whether the null-
 * terminated whole fits in destsize.  Your list of string pointers must end
 * with a NULL to indicate the end of the list. */
size_t stringjoin(char *dest, size_t destsize, ...)
{
	va_list ap;
	size_t len, ret = 0;
	const char *src;

	va_start(ap, destsize);
	while (1) {
		if (!(src = va_arg(ap, const char *)))
			break;
		len = strlen(src);
		ret += len;
		if (destsize > 1) {
			if (len >= destsize)
				len = destsize - 1;
			memcpy(dest, src, len);
			destsize -= len;
			dest += len;
		}
	}
	*dest = '\0';
	va_end(ap);

	return ret;
}

int count_dir_elements(const char *p)
{
	int cnt = 0, new_component = 1;
	while (*p) {
		if (*p++ == '/')
			new_component = (*p != '.' || (p[1] != '/' && p[1] != '\0'));
		else if (new_component) {
			new_component = 0;
			cnt++;
		}
	}
	return cnt;
}

/* Turns multiple adjacent slashes into a single slash (possible exception:
 * the preserving of two leading slashes at the start), drops all leading or
 * interior "." elements unless CFN_KEEP_DOT_DIRS is flagged.  Will also drop
 * a trailing '.' after a '/' if CFN_DROP_TRAILING_DOT_DIR is flagged, removes
 * a trailing slash (perhaps after removing the aforementioned dot) unless
 * CFN_KEEP_TRAILING_SLASH is flagged, and will also collapse ".." elements
 * (except at the start) if CFN_COLLAPSE_DOT_DOT_DIRS is flagged.  If the
 * resulting name would be empty, returns ".". */
int clean_fname(char *name, int flags)
{
	char *limit = name - 1, *t = name, *f = name;
	int anchored;

	if (!name)
		return 0;

#define DOT_IS_DOT_DOT_DIR(bp) (bp[1] == '.' && (bp[2] == '/' || !bp[2]))

	if ((anchored = *f == '/') != 0) {
		*t++ = *f++;
#ifdef __CYGWIN__
		/* If there are exactly 2 slashes at the start, preserve
		 * them.  Would break daemon excludes unless the paths are
		 * really treated differently, so used this sparingly. */
		if (*f == '/' && f[1] != '/')
			*t++ = *f++;
#endif
	} else if (flags & CFN_KEEP_DOT_DIRS && *f == '.' && f[1] == '/') {
		*t++ = *f++;
		*t++ = *f++;
	} else if (flags & CFN_REFUSE_DOT_DOT_DIRS && *f == '.' && DOT_IS_DOT_DOT_DIR(f))
		return -1;
	while (*f) {
		/* discard extra slashes */
		if (*f == '/') {
			f++;
			continue;
		}
		if (*f == '.') {
			/* discard interior "." dirs */
			if (f[1] == '/' && !(flags & CFN_KEEP_DOT_DIRS)) {
				f += 2;
				continue;
			}
			if (f[1] == '\0' && flags & CFN_DROP_TRAILING_DOT_DIR)
				break;
			/* collapse ".." dirs */
			if (flags & (CFN_COLLAPSE_DOT_DOT_DIRS|CFN_REFUSE_DOT_DOT_DIRS) && DOT_IS_DOT_DOT_DIR(f)) {
				char *s = t - 1;
				if (flags & CFN_REFUSE_DOT_DOT_DIRS)
					return -1;
				if (s == name && anchored) {
					f += 2;
					continue;
				}
				while (s > limit && *--s != '/') {}
				if (s != t - 1 && (s < name || *s == '/')) {
					t = s + 1;
					f += 2;
					continue;
				}
				limit = t + 2;
			}
		}
		while (*f && (*t++ = *f++) != '/') {}
	}

	if (t > name+anchored && t[-1] == '/' && !(flags & CFN_KEEP_TRAILING_SLASH))
		t--;
	if (t == name)
		*t++ = '.';
	*t = '\0';

#undef DOT_IS_DOT_DOT_DIR

	return t - name;
}

/* Make path appear as if a chroot had occurred.  This handles a leading
 * "/" (either removing it or expanding it) and any leading or embedded
 * ".." components that attempt to escape past the module's top dir.
 *
 * If dest is NULL, a buffer is allocated to hold the result.  It is legal
 * to call with the dest and the path (p) pointing to the same buffer, but
 * rootdir will be ignored to avoid expansion of the string.
 *
 * The rootdir string contains a value to use in place of a leading slash.
 * Specify NULL to get the default of "module_dir".
 *
 * The depth var is a count of how many '..'s to allow at the start of the
 * path.
 *
 * We also clean the path in a manner similar to clean_fname() but with a
 * few differences:
 *
 * Turns multiple adjacent slashes into a single slash, gets rid of "." dir
 * elements (INCLUDING a trailing dot dir), PRESERVES a trailing slash, and
 * ALWAYS collapses ".." elements (except for those at the start of the
 * string up to "depth" deep).  If the resulting name would be empty,
 * change it into a ".". */
char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth, int flags)
{
	char *start, *sanp;
	int rlen = 0, drop_dot_dirs = !relative_paths || !(flags & SP_KEEP_DOT_DIRS);

	if (dest != p) {
		int plen = strlen(p); /* the path len INCLUDING any separating slash */
		if (*p == '/') {
			if (!rootdir)
				rootdir = module_dir;
			rlen = strlen(rootdir);
			depth = 0;
			p++;
		}
		if (!dest)
			dest = new_array(char, MAX(rlen + plen + 1, 2));
		else if (rlen + plen + 1 >= MAXPATHLEN)
			return NULL;
		if (rlen) { /* only true if p previously started with a slash */
			memcpy(dest, rootdir, rlen);
			if (rlen > 1) /* a rootdir of len 1 is "/", so this avoids a 2nd slash */
				dest[rlen++] = '/';
		}
	}

	if (drop_dot_dirs) {
		while (*p == '.' && p[1] == '/')
			p += 2;
	}

	start = sanp = dest + rlen;
	/* This loop iterates once per filename component in p, pointing at
	 * the start of the name (past any prior slash) for each iteration. */
	while (*p) {
		/* discard leading or extra slashes */
		if (*p == '/') {
			p++;
			continue;
		}
		if (drop_dot_dirs) {
			if (*p == '.' && (p[1] == '/' || p[1] == '\0')) {
				/* skip "." component */
				p++;
				continue;
			}
		}
		if (*p == '.' && p[1] == '.' && (p[2] == '/' || p[2] == '\0')) {
			/* ".." component followed by slash or end */
			if (depth <= 0 || sanp != start) {
				p += 2;
				if (sanp != start) {
					/* back up sanp one level */
					--sanp; /* now pointing at slash */
					while (sanp > start && sanp[-1] != '/')
						sanp--;
				}
				continue;
			}
			/* allow depth levels of .. at the beginning */
			depth--;
			/* move the virtual beginning to leave the .. alone */
			start = sanp + 3;
		}
		/* copy one component through next slash */
		while (*p && (*sanp++ = *p++) != '/') {}
	}
	if (sanp == dest) {
		/* ended up with nothing, so put in "." component */
		*sanp++ = '.';
	}
	*sanp = '\0';

	return dest;
}

/* Like chdir(), but it keeps track of the current directory (in the
 * global "curr_dir"), and ensures that the path size doesn't overflow.
 * Also cleans the path using the clean_fname() function. */
int change_dir(const char *dir, int set_path_only)
{
	static int initialised, skipped_chdir;
	unsigned int len;

	if (!initialised) {
		initialised = 1;
		if (getcwd(curr_dir, sizeof curr_dir - 1) == NULL) {
			rsyserr(FERROR, errno, "getcwd()");
			exit_cleanup(RERR_FILESELECT);
		}
		curr_dir_len = strlen(curr_dir);
	}

	if (!dir)	/* this call was probably just to initialize */
		return 0;

	len = strlen(dir);
	if (len == 1 && *dir == '.' && (!skipped_chdir || set_path_only))
		return 1;

	if (*dir == '/') {
		if (len >= sizeof curr_dir) {
			errno = ENAMETOOLONG;
			return 0;
		}
		if (!set_path_only && chdir(dir))
			return 0;
		skipped_chdir = set_path_only;
		memcpy(curr_dir, dir, len + 1);
	} else {
		unsigned int save_dir_len = curr_dir_len;
		if (curr_dir_len + 1 + len >= sizeof curr_dir) {
			errno = ENAMETOOLONG;
			return 0;
		}
		if (!(curr_dir_len && curr_dir[curr_dir_len-1] == '/'))
			curr_dir[curr_dir_len++] = '/';
		memcpy(curr_dir + curr_dir_len, dir, len + 1);

		if (!set_path_only && chdir(curr_dir)) {
			curr_dir_len = save_dir_len;
			curr_dir[curr_dir_len] = '\0';
			return 0;
		}
		skipped_chdir = set_path_only;
	}

	curr_dir_len = clean_fname(curr_dir, CFN_COLLAPSE_DOT_DOT_DIRS | CFN_DROP_TRAILING_DOT_DIR);
	if (sanitize_paths) {
		if (module_dirlen > curr_dir_len)
			module_dirlen = curr_dir_len;
		curr_dir_depth = count_dir_elements(curr_dir + module_dirlen);
	}

	if (DEBUG_GTE(CHDIR, 1) && !set_path_only)
		rprintf(FINFO, "[%s] change_dir(%s)\n", who_am_i(), curr_dir);

	return 1;
}

/* This will make a relative path absolute and clean it up via clean_fname().
 * Returns the string, which might be newly allocated, or NULL on error. */
char *normalize_path(char *path, BOOL force_newbuf, unsigned int *len_ptr)
{
	unsigned int len;

	if (*path != '/') { /* Make path absolute. */
		int len = strlen(path);
		if (curr_dir_len + 1 + len >= sizeof curr_dir)
			return NULL;
		curr_dir[curr_dir_len] = '/';
		memcpy(curr_dir + curr_dir_len + 1, path, len + 1);
		path = strdup(curr_dir);
		curr_dir[curr_dir_len] = '\0';
	} else if (force_newbuf)
		path = strdup(path);

	len = clean_fname(path, CFN_COLLAPSE_DOT_DOT_DIRS | CFN_DROP_TRAILING_DOT_DIR);

	if (len_ptr)
		*len_ptr = len;

	return path;
}

/**
 * Return a quoted string with the full pathname of the indicated filename.
 * The string " (in MODNAME)" may also be appended.  The returned pointer
 * remains valid until the next time full_fname() is called.
 **/
char *full_fname(const char *fn)
{
	static char *result = NULL;
	char *m1, *m2, *m3;
	char *p1, *p2;

	if (result)
		free(result);

	if (*fn == '/')
		p1 = p2 = "";
	else {
		p1 = curr_dir + module_dirlen;
		for (p2 = p1; *p2 == '/'; p2++) {}
		if (*p2)
			p2 = "/";
	}
	if (module_id >= 0) {
		m1 = " (in ";
		m2 = lp_name(module_id);
		m3 = ")";
	} else
		m1 = m2 = m3 = "";

	if (asprintf(&result, "\"%s%s%s\"%s%s%s", p1, p2, fn, m1, m2, m3) < 0)
		out_of_memory("full_fname");

	return result;
}

static char partial_fname[MAXPATHLEN];

char *partial_dir_fname(const char *fname)
{
	char *t = partial_fname;
	int sz = sizeof partial_fname;
	const char *fn;

	if ((fn = strrchr(fname, '/')) != NULL) {
		fn++;
		if (*partial_dir != '/') {
			int len = fn - fname;
			strncpy(t, fname, len); /* safe */
			t += len;
			sz -= len;
		}
	} else
		fn = fname;
	if ((int)pathjoin(t, sz, partial_dir, fn) >= sz)
		return NULL;
	if (daemon_filter_list.head) {
		t = strrchr(partial_fname, '/');
		*t = '\0';
		if (check_filter(&daemon_filter_list, FLOG, partial_fname, 1) < 0)
			return NULL;
		*t = '/';
		if (check_filter(&daemon_filter_list, FLOG, partial_fname, 0) < 0)
			return NULL;
	}

	return partial_fname;
}

/* If no --partial-dir option was specified, we don't need to do anything
 * (the partial-dir is essentially '.'), so just return success. */
int handle_partial_dir(const char *fname, int create)
{
	char *fn, *dir;

	if (fname != partial_fname)
		return 1;
	if (!create && *partial_dir == '/')
		return 1;
	if (!(fn = strrchr(partial_fname, '/')))
		return 1;

	*fn = '\0';
	dir = partial_fname;
	if (create) {
		STRUCT_STAT st;
		int statret = do_lstat(dir, &st);
		if (statret == 0 && !S_ISDIR(st.st_mode)) {
			if (do_unlink(dir) < 0) {
				*fn = '/';
				return 0;
			}
			statret = -1;
		}
		if (statret < 0 && do_mkdir(dir, 0700) < 0) {
			*fn = '/';
			return 0;
		}
	} else
		do_rmdir(dir);
	*fn = '/';

	return 1;
}

/* Determine if a symlink points outside the current directory tree.
 * This is considered "unsafe" because e.g. when mirroring somebody
 * else's machine it might allow them to establish a symlink to
 * /etc/passwd, and then read it through a web server.
 *
 * Returns 1 if unsafe, 0 if safe.
 *
 * Null symlinks and absolute symlinks are always unsafe.
 *
 * Basically here we are concerned with symlinks whose target contains
 * "..", because this might cause us to walk back up out of the
 * transferred directory.  We are not allowed to go back up and
 * reenter.
 *
 * "dest" is the target of the symlink in question.
 *
 * "src" is the top source directory currently applicable at the level
 * of the referenced symlink.  This is usually the symlink's full path
 * (including its name), as referenced from the root of the transfer.
 *
 * NOTE: this also rejects dest names with a .. component in other
 * than the first component of the name ie. it rejects names such as
 * a/b/../x/y. This needs to be done as the leading subpaths 'a' or
 * 'b' could later be replaced with symlinks such as a link to '.'
 * resulting in the link being transferred now becoming unsafe
 */
int unsafe_symlink(const char *dest, const char *src)
{
	const char *name, *slash;
	int depth = 0;

	/* all absolute and null symlinks are unsafe */
	if (!dest || !*dest || *dest == '/')
		return 1;

	// reject destinations with /../ in the name other than at the start of the name
	const char *dest2 = dest;
	while (strncmp(dest2, "../", 3) == 0) {
	    dest2 += 3;
	    while (*dest2 == '/') {
		// allow for ..//..///../foo
		dest2++;
	    }
	}
	if (strstr(dest2, "/../"))
	    return 1;

	// reject if the destination ends in /..
	const size_t dlen = strlen(dest);
	if (dlen > 3 && strcmp(&dest[dlen-3], "/..") == 0)
	    return 1;

	/* find out what our safety margin is */
	for (name = src; (slash = strchr(name, '/')) != 0; name = slash+1) {
		/* ".." segment starts the count over.  "." segment is ignored. */
		if (*name == '.' && (name[1] == '/' || (name[1] == '.' && name[2] == '/'))) {
			if (name[1] == '.')
				depth = 0;
		} else
			depth++;
		while (slash[1] == '/') slash++; /* just in case src isn't clean */
	}
	if (*name == '.' && name[1] == '.' && name[2] == '\0')
		depth = 0;

	for (name = dest; (slash = strchr(name, '/')) != 0; name = slash+1) {
		if (*name == '.' && (name[1] == '/' || (name[1] == '.' && name[2] == '/'))) {
			if (name[1] == '.') {
				/* if at any point we go outside the current directory
				   then stop - it is unsafe */
				if (--depth < 0)
					return 1;
			}
		} else
			depth++;
		while (slash[1] == '/') slash++;
	}
	if (*name == '.' && name[1] == '.' && name[2] == '\0')
		depth--;

	return depth < 0;
}

/* Return the date and time as a string.  Some callers tweak returned buf. */
char *timestring(time_t t)
{
	static int ndx = 0;
	static char buffers[4][20]; /* We support 4 simultaneous timestring results. */
	char *TimeBuf = buffers[ndx = (ndx + 1) % 4];
	struct tm *tm = localtime(&t);
	int len = snprintf(TimeBuf, sizeof buffers[0], "%4d/%02d/%02d %02d:%02d:%02d",
		 (int)tm->tm_year + 1900, (int)tm->tm_mon + 1, (int)tm->tm_mday,
		 (int)tm->tm_hour, (int)tm->tm_min, (int)tm->tm_sec);
	assert(len > 0); /* Silence gcc warning */

	return TimeBuf;
}

/* Determine if two time_t values are equivalent (either exact, or in
 * the modification timestamp window established by --modify-window).
 * Returns 1 if the times the "same", or 0 if they are different. */
int same_time(time_t f1_sec, unsigned long f1_nsec, time_t f2_sec, unsigned long f2_nsec)
{
	if (modify_window == 0)
		return f1_sec == f2_sec;
	if (modify_window < 0)
		return f1_sec == f2_sec && f1_nsec == f2_nsec;
	/* The nanoseconds do not figure into these checks -- time windows don't care about that. */
	if (f2_sec > f1_sec)
		return f2_sec - f1_sec <= modify_window;
	return f1_sec - f2_sec <= modify_window;
}

#ifdef __INSURE__XX
#include <dlfcn.h>

/**
   This routine is a trick to immediately catch errors when debugging
   with insure. A xterm with a gdb is popped up when insure catches
   a error. It is Linux specific.
**/
int _Insure_trap_error(int a1, int a2, int a3, int a4, int a5, int a6)
{
	static int (*fn)();
	int ret, pid_int = getpid();
	char *cmd;

	if (asprintf(&cmd,
	    "/usr/X11R6/bin/xterm -display :0 -T Panic -n Panic -e /bin/sh -c 'cat /tmp/ierrs.*.%d ; "
	    "gdb /proc/%d/exe %d'", pid_int, pid_int, pid_int) < 0)
		return -1;

	if (!fn) {
		static void *h;
		h = dlopen("/usr/local/parasoft/insure++lite/lib.linux2/libinsure.so", RTLD_LAZY);
		fn = dlsym(h, "_Insure_trap_error");
	}

	ret = fn(a1, a2, a3, a4, a5, a6);

	system(cmd);

	free(cmd);

	return ret;
}
#endif

/* Take a filename and filename length and return the most significant
 * filename suffix we can find.  This ignores suffixes such as "~",
 * ".bak", ".orig", ".~1~", etc. */
const char *find_filename_suffix(const char *fn, int fn_len, int *len_ptr)
{
	const char *suf, *s;
	BOOL had_tilde;
	int s_len;

	/* One or more dots at the start aren't a suffix. */
	while (fn_len && *fn == '.') fn++, fn_len--;

	/* Ignore the ~ in a "foo~" filename. */
	if (fn_len > 1 && fn[fn_len-1] == '~')
		fn_len--, had_tilde = True;
	else
		had_tilde = False;

	/* Assume we don't find an suffix. */
	suf = "";
	*len_ptr = 0;

	/* Find the last significant suffix. */
	for (s = fn + fn_len; fn_len > 1; ) {
		while (*--s != '.' && s != fn) {}
		if (s == fn)
			break;
		s_len = fn_len - (s - fn);
		fn_len = s - fn;
		if (s_len == 4) {
			if (strcmp(s+1, "bak") == 0
			 || strcmp(s+1, "old") == 0)
				continue;
		} else if (s_len == 5) {
			if (strcmp(s+1, "orig") == 0)
				continue;
		} else if (s_len > 2 && had_tilde && s[1] == '~' && isDigit(s + 2))
			continue;
		*len_ptr = s_len;
		suf = s;
		if (s_len == 1)
			break;
		/* Determine if the suffix is all digits. */
		for (s++, s_len--; s_len > 0; s++, s_len--) {
			if (!isDigit(s))
				return suf;
		}
		/* An all-digit suffix may not be that significant. */
		s = suf;
	}

	return suf;
}

/* This is an implementation of the Levenshtein distance algorithm.  It
 * was implemented to avoid needing a two-dimensional matrix (to save
 * memory).  It was also tweaked to try to factor in the ASCII distance
 * between changed characters as a minor distance quantity.  The normal
 * Levenshtein units of distance (each signifying a single change between
 * the two strings) are defined as a "UNIT". */

#define UNIT (1 << 16)

uint32 fuzzy_distance(const char *s1, unsigned len1, const char *s2, unsigned len2, uint32 upperlimit)
{
	uint32 a[MAXPATHLEN], diag, above, left, diag_inc, above_inc, left_inc;
	int32 cost;
	unsigned i1, i2;

	/* Check to see if the Levenshtein distance must be greater than the
	 * upper limit defined by the previously found lowest distance using
	 * the heuristic that the Levenshtein distance is greater than the
	 * difference in length of the two strings */
	if ((len1 > len2 ? len1 - len2 : len2 - len1) * UNIT > upperlimit)
		return 0xFFFFU * UNIT + 1;

	if (!len1 || !len2) {
		if (!len1) {
			s1 = s2;
			len1 = len2;
		}
		for (i1 = 0, cost = 0; i1 < len1; i1++)
			cost += s1[i1];
		return (int32)len1 * UNIT + cost;
	}

	for (i2 = 0; i2 < len2; i2++)
		a[i2] = (i2+1) * UNIT;

	for (i1 = 0; i1 < len1; i1++) {
		diag = i1 * UNIT;
		above = (i1+1) * UNIT;
		for (i2 = 0; i2 < len2; i2++) {
			left = a[i2];
			if ((cost = *((uchar*)s1+i1) - *((uchar*)s2+i2)) != 0) {
				if (cost < 0)
					cost = UNIT - cost;
				else
					cost = UNIT + cost;
			}
			diag_inc = diag + cost;
			left_inc = left + UNIT + *((uchar*)s1+i1);
			above_inc = above + UNIT + *((uchar*)s2+i2);
			a[i2] = above = left < above
			      ? (left_inc < diag_inc ? left_inc : diag_inc)
			      : (above_inc < diag_inc ? above_inc : diag_inc);
			diag = left;
		}
	}

	return a[len2-1];
}

#define BB_SLOT_SIZE     (16*1024)          /* Desired size in bytes */
#define BB_PER_SLOT_BITS (BB_SLOT_SIZE * 8) /* Number of bits per slot */
#define BB_PER_SLOT_INTS (BB_SLOT_SIZE / 4) /* Number of int32s per slot */

struct bitbag {
	uint32 **bits;
	int slot_cnt;
};

struct bitbag *bitbag_create(int max_ndx)
{
	struct bitbag *bb = new(struct bitbag);
	bb->slot_cnt = (max_ndx + BB_PER_SLOT_BITS - 1) / BB_PER_SLOT_BITS;

	bb->bits = new_array0(uint32*, bb->slot_cnt);

	return bb;
}

void bitbag_set_bit(struct bitbag *bb, int ndx)
{
	int slot = ndx / BB_PER_SLOT_BITS;
	ndx %= BB_PER_SLOT_BITS;

	if (!bb->bits[slot])
		bb->bits[slot] = new_array0(uint32, BB_PER_SLOT_INTS);

	bb->bits[slot][ndx/32] |= 1u << (ndx % 32);
}

#if 0 /* not needed yet */
void bitbag_clear_bit(struct bitbag *bb, int ndx)
{
	int slot = ndx / BB_PER_SLOT_BITS;
	ndx %= BB_PER_SLOT_BITS;

	if (!bb->bits[slot])
		return;

	bb->bits[slot][ndx/32] &= ~(1u << (ndx % 32));
}

int bitbag_check_bit(struct bitbag *bb, int ndx)
{
	int slot = ndx / BB_PER_SLOT_BITS;
	ndx %= BB_PER_SLOT_BITS;

	if (!bb->bits[slot])
		return 0;

	return bb->bits[slot][ndx/32] & (1u << (ndx % 32)) ? 1 : 0;
}
#endif

/* Call this with -1 to start checking from 0.  Returns -1 at the end. */
int bitbag_next_bit(struct bitbag *bb, int after)
{
	uint32 bits, mask;
	int i, ndx = after + 1;
	int slot = ndx / BB_PER_SLOT_BITS;
	ndx %= BB_PER_SLOT_BITS;

	mask = (1u << (ndx % 32)) - 1;
	for (i = ndx / 32; slot < bb->slot_cnt; slot++, i = mask = 0) {
		if (!bb->bits[slot])
			continue;
		for ( ; i < BB_PER_SLOT_INTS; i++, mask = 0) {
			if (!(bits = bb->bits[slot][i] & ~mask))
				continue;
			/* The xor magic figures out the lowest enabled bit in
			 * bits, and the switch quickly computes log2(bit). */
			switch (bits ^ (bits & (bits-1))) {
#define LOG2(n) case 1u << n: return slot*BB_PER_SLOT_BITS + i*32 + n
			    LOG2(0);  LOG2(1);  LOG2(2);  LOG2(3);
			    LOG2(4);  LOG2(5);  LOG2(6);  LOG2(7);
			    LOG2(8);  LOG2(9);  LOG2(10); LOG2(11);
			    LOG2(12); LOG2(13); LOG2(14); LOG2(15);
			    LOG2(16); LOG2(17); LOG2(18); LOG2(19);
			    LOG2(20); LOG2(21); LOG2(22); LOG2(23);
			    LOG2(24); LOG2(25); LOG2(26); LOG2(27);
			    LOG2(28); LOG2(29); LOG2(30); LOG2(31);
			}
			return -1; /* impossible... */
		}
	}

	return -1;
}

void flist_ndx_push(flist_ndx_list *lp, int ndx)
{
	struct flist_ndx_item *item;

	item = new(struct flist_ndx_item);
	item->next = NULL;
	item->ndx = ndx;
	if (lp->tail)
		lp->tail->next = item;
	else
		lp->head = item;
	lp->tail = item;
}

int flist_ndx_pop(flist_ndx_list *lp)
{
	struct flist_ndx_item *next;
	int ndx;

	if (!lp->head)
		return -1;

	ndx = lp->head->ndx;
	next = lp->head->next;
	free(lp->head);
	lp->head = next;
	if (!next)
		lp->tail = NULL;

	return ndx;
}

/* Make sure there is room for one more item in the item list.  If there
 * is not, expand the list as indicated by the value of "incr":
 *  - if incr < 0 then increase the malloced size by -1 * incr
 *  - if incr >= 0 then either make the malloced size equal to "incr"
 *    or (if that's not large enough) double the malloced size
 * After the size check, the list's count is incremented by 1 and a pointer
 * to the "new" list item is returned.
 */
void *expand_item_list(item_list *lp, size_t item_size, const char *desc, int incr)
{
	/* First time through, 0 <= 0, so list is expanded. */
	if (lp->malloced <= lp->count) {
		void *new_ptr;
		size_t expand_size;
		if (incr < 0)
			expand_size = -incr; /* increase slowly */
		else if (lp->malloced < (size_t)incr)
			expand_size = incr - lp->malloced;
		else if (lp->malloced)
			expand_size = lp->malloced; /* double in size */
		else
			expand_size = 1;
		if (SIZE_MAX/item_size - expand_size < lp->malloced)
			overflow_exit("expand_item_list");
		expand_size += lp->malloced;
		new_ptr = realloc_buf(lp->items, expand_size * item_size);
		if (DEBUG_GTE(FLIST, 3)) {
			rprintf(FINFO, "[%s] expand %s to %s bytes, did%s move\n",
				who_am_i(), desc, big_num(expand_size * item_size),
				new_ptr == lp->items ? " not" : "");
		}

		lp->items = new_ptr;
		lp->malloced = expand_size;
	}
	return (char*)lp->items + (lp->count++ * item_size);
}

/* This zeroing of memory won't be optimized away by the compiler. */
void force_memzero(void *buf, size_t len)
{
	volatile uchar *z = buf;
	while (len-- > 0)
		*z++ = '\0';
}
/*
 * Utility routines used in rsync.
 *
 * Copyright (C) 1996-2000 Andrew Tridgell
 * Copyright (C) 1996 Paul Mackerras
 * Copyright (C) 2001, 2002 Martin Pool <mbp@samba.org>
 * Copyright (C) 2003-2024 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"
#include "itypes.h"
#include "inums.h"

extern size_t max_alloc;

char *do_calloc = "42";

/**
 * Sleep for a specified number of milliseconds.
 *
 * Always returns True.
 **/
int msleep(int t)
{
#ifdef HAVE_NANOSLEEP
	struct timespec ts;

	ts.tv_sec = t / 1000;
	ts.tv_nsec = (t % 1000) * 1000000L;

	while (nanosleep(&ts, &ts) < 0 && errno == EINTR) {}

#elif defined HAVE_USLEEP
	usleep(t*1000);

#else
	int tdiff = 0;
	struct timeval tval, t1, t2;

	gettimeofday(&t1, NULL);

	while (tdiff < t) {
		tval.tv_sec = (t-tdiff)/1000;
		tval.tv_usec = 1000*((t-tdiff)%1000);

		errno = 0;
		select(0,NULL,NULL, NULL, &tval);

		gettimeofday(&t2, NULL);
		tdiff = (t2.tv_sec - t1.tv_sec)*1000 +
			(t2.tv_usec - t1.tv_usec)/1000;
		if (tdiff < 0)
			t1 = t2; /* Time went backwards, so start over. */
	}
#endif

	return True;
}

void *my_alloc(void *ptr, size_t num, size_t size, const char *file, int line)
{
	if (num >= max_alloc/size) {
		if (!file)
			return NULL;
		rprintf(FERROR, "[%s] exceeded --max-alloc=%s setting (file=%s, line=%d)\n",
			who_am_i(), do_big_num(max_alloc, 0, NULL), src_file(file), line);
		exit_cleanup(RERR_MALLOC);
	}
	if (!ptr)
		ptr = malloc(num * size);
	else if (ptr == do_calloc)
		ptr = calloc(num, size);
	else
		ptr = realloc(ptr, num * size);
	if (!ptr && file)
		_out_of_memory("my_alloc caller", file, line);
	return ptr;
}

const char *sum_as_hex(int csum_type, const char *sum, int flist_csum)
{
	static char buf[MAX_DIGEST_LEN*2+1];
	int i, x1, x2;
	int canonical = canonical_checksum(csum_type);
	int sum_len = csum_len_for_type(csum_type, flist_csum);
	char *c;

	if (!canonical)
		return NULL;

	assert(sum_len*2 < (int)sizeof buf);

	for (i = sum_len, c = buf; --i >= 0; ) {
		int ndx = canonical < 0 ? sum_len - i - 1 : i;
		x2 = CVAL(sum, ndx);
		x1 = x2 >> 4;
		x2 &= 0xF;
		*c++ = x1 <= 9 ? x1 + '0' : x1 + 'a' - 10;
		*c++ = x2 <= 9 ? x2 + '0' : x2 + 'a' - 10;
	}

	*c = '\0';

	return buf;
}

NORETURN void _out_of_memory(const char *msg, const char *file, int line)
{
	rprintf(FERROR, "[%s] out of memory: %s (file=%s, line=%d)\n", who_am_i(), msg, src_file(file), line);
	exit_cleanup(RERR_MALLOC);
}

NORETURN void _overflow_exit(const char *msg, const char *file, int line)
{
	rprintf(FERROR, "[%s] buffer overflow: %s (file=%s, line=%d)\n", who_am_i(), msg, src_file(file), line);
	exit_cleanup(RERR_MALLOC);
}

const char *src_file(const char *file)
{
	static const char *util2 = __FILE__;
	static int prefix = -1;

	if (prefix < 0) {
		const char *cp = strrchr(util2, '/');
		prefix = cp ? cp - util2 + 1 : 0;
	}

	if (prefix && strncmp(file, util2, prefix) == 0)
		return file + prefix;
	return file;
}
/*
 * Test suite for the wildmatch code.
 *
 * Copyright (C) 2003-2019 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

/*#define COMPARE_WITH_FNMATCH*/

#define WILD_TEST_ITERATIONS
#include "lib/wildmatch.c"

#include <popt.h>

#ifdef COMPARE_WITH_FNMATCH
#include <fnmatch.h>

int fnmatch_errors = 0;
#endif

int wildmatch_errors = 0;

typedef char bool;

int output_iterations = 0;
int explode_mod = 0;
int empties_mod = 0;
int empty_at_start = 0;
int empty_at_end = 0;

static struct poptOption long_options[] = {
  /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
  {"iterations",     'i', POPT_ARG_NONE,   &output_iterations, 0, 0, 0},
  {"empties",        'e', POPT_ARG_STRING, 0, 'e', 0, 0},
  {"explode",        'x', POPT_ARG_INT,    &explode_mod, 0, 0, 0},
  {0,0,0,0, 0, 0, 0}
};

/* match just at the start of string (anchored tests) */
static void
run_test(int line, bool matches,
#ifdef COMPARE_WITH_FNMATCH
	 bool same_as_fnmatch,
#endif
	 const char *text, const char *pattern)
{
    bool matched;
#ifdef COMPARE_WITH_FNMATCH
    bool fn_matched;
    int flags = strstr(pattern, "**")? 0 : FNM_PATHNAME;
#endif

    if (explode_mod) {
	char buf[MAXPATHLEN*2], *texts[MAXPATHLEN];
	int pos = 0, cnt = 0, ndx = 0, len = strlen(text);

	if (empty_at_start)
	    texts[ndx++] = "";
	/* An empty string must turn into at least one empty array item. */
	while (1) {
	    texts[ndx] = buf + ndx * (explode_mod + 1);
	    strlcpy(texts[ndx++], text + pos, explode_mod + 1);
	    if (pos + explode_mod >= len)
		break;
	    pos += explode_mod;
	    if (!(++cnt % empties_mod))
		texts[ndx++] = "";
	}
	if (empty_at_end)
	    texts[ndx++] = "";
	texts[ndx] = NULL;
	matched = wildmatch_array(pattern, (const char**)texts, 0);
    } else
	matched = wildmatch(pattern, text);
#ifdef COMPARE_WITH_FNMATCH
    fn_matched = !fnmatch(pattern, text, flags);
#endif
    if (matched != matches) {
	printf("wildmatch failure on line %d:\n  %s\n  %s\n  expected %s match\n",
	       line, text, pattern, matches? "a" : "NO");
	wildmatch_errors++;
    }
#ifdef COMPARE_WITH_FNMATCH
    if (fn_matched != (matches ^ !same_as_fnmatch)) {
	printf("fnmatch disagreement on line %d:\n  %s\n  %s\n  expected %s match\n",
	       line, text, pattern, matches ^ !same_as_fnmatch? "a" : "NO");
	fnmatch_errors++;
    }
#endif
    if (output_iterations) {
	printf("%d: \"%s\" iterations = %d\n", line, pattern,
	       wildmatch_iteration_count);
    }
}

int
main(int argc, char **argv)
{
    char buf[2048], *s, *string[2], *end[2];
    const char *arg;
    FILE *fp;
    int opt, line, i, flag[2];
    poptContext pc = poptGetContext("wildtest", argc, (const char**)argv,
				    long_options, 0);

    while ((opt = poptGetNextOpt(pc)) != -1) {
	switch (opt) {
	  case 'e':
	    arg = poptGetOptArg(pc);
	    empties_mod = atoi(arg);
	    if (strchr(arg, 's'))
		empty_at_start = 1;
	    if (strchr(arg, 'e'))
		empty_at_end = 1;
	    if (!explode_mod)
		explode_mod = 1024;
	    break;
	  default:
	    fprintf(stderr, "%s: %s\n",
		    poptBadOption(pc, POPT_BADOPTION_NOALIAS),
		    poptStrerror(opt));
	    exit(1);
	}
    }

    if (explode_mod && !empties_mod)
	empties_mod = 1024;

    argv = (char**)poptGetArgs(pc);
    if (!argv || argv[1]) {
	fprintf(stderr, "Usage: wildtest [OPTIONS] TESTFILE\n");
	exit(1);
    }

    if ((fp = fopen(*argv, "r")) == NULL) {
	fprintf(stderr, "Unable to open %s\n", *argv);
	exit(1);
    }

    line = 0;
    while (fgets(buf, sizeof buf, fp)) {
	line++;
	if (*buf == '#' || *buf == '\n')
	    continue;
	for (s = buf, i = 0; i <= 1; i++) {
	    if (*s == '1')
		flag[i] = 1;
	    else if (*s == '0')
		flag[i] = 0;
	    else
		flag[i] = -1;
	    if (*++s != ' ' && *s != '\t')
		flag[i] = -1;
	    if (flag[i] < 0) {
		fprintf(stderr, "Invalid flag syntax on line %d of %s:\n%s",
			line, *argv, buf);
		exit(1);
	    }
	    while (*++s == ' ' || *s == '\t') {}
	}
	for (i = 0; i <= 1; i++) {
	    if (*s == '\'' || *s == '"' || *s == '`') {
		char quote = *s++;
		string[i] = s;
		while (*s && *s != quote) s++;
		if (!*s) {
		    fprintf(stderr, "Unmatched quote on line %d of %s:\n%s",
			    line, *argv, buf);
		    exit(1);
		}
		end[i] = s;
	    }
	    else {
		if (!*s || *s == '\n') {
		    fprintf(stderr, "Not enough strings on line %d of %s:\n%s",
			    line, *argv, buf);
		    exit(1);
		}
		string[i] = s;
		while (*++s && *s != ' ' && *s != '\t' && *s != '\n') {}
		end[i] = s;
	    }
	    while (*++s == ' ' || *s == '\t') {}
	}
	*end[0] = *end[1] = '\0';
	run_test(line, flag[0],
#ifdef COMPARE_WITH_FNMATCH
		 flag[1],
#endif
		 string[0], string[1]);
    }

    if (!wildmatch_errors)
	fputs("No", stdout);
    else
	printf("%d", wildmatch_errors);
    printf(" wildmatch error%s found.\n", wildmatch_errors == 1? "" : "s");

#ifdef COMPARE_WITH_FNMATCH
    if (!fnmatch_errors)
	fputs("No", stdout);
    else
	printf("%d", fnmatch_errors);
    printf(" fnmatch error%s found.\n", fnmatch_errors == 1? "" : "s");

#endif

    return 0;
}
/*
 * Extended Attribute support for rsync.
 * Written by Jay Fenlason, vaguely based on the ACLs patch.
 *
 * Copyright (C) 2004 Red Hat, Inc.
 * Copyright (C) 2006-2022 Wayne Davison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, visit the http://fsf.org website.
 */

#include "rsync.h"
#include "ifuncs.h"
#include "inums.h"
#include "lib/sysxattrs.h"

#ifdef SUPPORT_XATTRS

extern int dry_run;
extern int am_root;
extern int am_sender;
extern int am_generator;
extern int read_only;
extern int list_only;
extern int preserve_xattrs;
extern int preserve_links;
extern int preserve_devices;
extern int preserve_specials;
extern int checksum_seed;
extern int saw_xattr_filter;

extern struct name_num_item *xattr_sum_nni;
extern int xattr_sum_len;

#define RSYNC_XAL_INITIAL 5
#define RSYNC_XAL_LIST_INITIAL 100

#define MAX_XATTR_DIGEST_LEN MD5_DIGEST_LEN
#define MAX_FULL_DATUM 32

#define HAS_PREFIX(str, prfx) (*(str) == *(prfx) && strncmp(str, prfx, sizeof (prfx) - 1) == 0)

#define XATTR_ABBREV(x) ((size_t)((x).name - (x).datum) < (x).datum_len)

#define XSTATE_ABBREV	1
#define XSTATE_DONE	2
#define XSTATE_TODO	3

#define USER_PREFIX "user."
#define UPRE_LEN ((int)sizeof USER_PREFIX - 1)
#define SYSTEM_PREFIX "system."
#define SPRE_LEN ((int)sizeof SYSTEM_PREFIX - 1)

#ifdef HAVE_LINUX_XATTRS
#define MIGHT_NEED_RPRE (am_root <= 0)
#define RSYNC_PREFIX USER_PREFIX "rsync."
#else
#define MIGHT_NEED_RPRE am_root
#define RSYNC_PREFIX "rsync."
#endif
#define RPRE_LEN ((int)sizeof RSYNC_PREFIX - 1)

#define XSTAT_SUFFIX "stat"
#define XSTAT_ATTR RSYNC_PREFIX "%" XSTAT_SUFFIX
#define XACC_ACL_SUFFIX "aacl"
#define XACC_ACL_ATTR RSYNC_PREFIX "%" XACC_ACL_SUFFIX
#define XDEF_ACL_SUFFIX "dacl"
#define XDEF_ACL_ATTR RSYNC_PREFIX "%" XDEF_ACL_SUFFIX

typedef struct {
	char *datum, *name;
	size_t datum_len, name_len;
	int num;
} rsync_xa;

struct _rsync_xa_list;

typedef struct _rsync_xa_list_ref {
	struct _rsync_xa_list_ref *next;
	int ndx;
} rsync_xa_list_ref;

typedef struct _rsync_xa_list {
	int ndx;
	int64 key;
	item_list xa_items;
} rsync_xa_list;

static size_t namebuf_len = 0;
static char *namebuf = NULL;

static const rsync_xa_list empty_xa_list = {
	.xa_items = EMPTY_ITEM_LIST,
};
static const item_list empty_xattr = EMPTY_ITEM_LIST;
static item_list rsync_xal_l = EMPTY_ITEM_LIST;
static struct hashtable *rsync_xal_h = NULL;

static size_t prior_xattr_count = (size_t)-1;

/* ------------------------------------------------------------------------- */

static void rsync_xal_free(item_list *xalp)
{
	size_t i;
	rsync_xa *rxas = xalp->items;

	if (!xalp->malloced)
		return;

	for (i = 0; i < xalp->count; i++) {
		free(rxas[i].datum);
		/*free(rxas[i].name);*/
	}
	free(xalp->items);
}

void free_xattr(stat_x *sxp)
{
	if (!sxp->xattr)
		return;
	rsync_xal_free(sxp->xattr);
	free(sxp->xattr);
	sxp->xattr = NULL;
}

static int rsync_xal_compare_names(const void *x1, const void *x2)
{
	const rsync_xa *xa1 = x1;
	const rsync_xa *xa2 = x2;
	return strcmp(xa1->name, xa2->name);
}

static ssize_t get_xattr_names(const char *fname)
{
	ssize_t list_len;
	int64 arg;

	if (!namebuf) {
		namebuf_len = 1024;
		namebuf = new_array(char, namebuf_len);
	}

	while (1) {
		/* The length returned includes all the '\0' terminators. */
		list_len = sys_llistxattr(fname, namebuf, namebuf_len);
		if (list_len >= 0) {
			if ((size_t)list_len <= namebuf_len)
				break;
		} else if (errno == ENOTSUP)
			return 0;
		else if (errno != ERANGE) {
			arg = namebuf_len;
		  got_error:
			rsyserr(FERROR_XFER, errno,
				"get_xattr_names: llistxattr(%s,%s) failed",
				full_fname(fname), big_num(arg));
			return -1;
		}
		list_len = sys_llistxattr(fname, NULL, 0);
		if (list_len < 0) {
			arg = 0;
			goto got_error;
		}
		if (namebuf_len)
			free(namebuf);
		namebuf_len = list_len + 1024;
		namebuf = new_array(char, namebuf_len);
	}

	return list_len;
}

/* On entry, the *len_ptr parameter contains the size of the extra space we
 * should allocate when we create a buffer for the data.  On exit, it contains
 * the length of the datum. */
static char *get_xattr_data(const char *fname, const char *name, size_t *len_ptr, int no_missing_error)
{
	size_t datum_len = sys_lgetxattr(fname, name, NULL, 0);
	size_t extra_len = *len_ptr;
	char *ptr;

	*len_ptr = datum_len;

	if (datum_len == (size_t)-1) {
		if (errno == ENOTSUP || no_missing_error)
			return NULL;
		rsyserr(FERROR_XFER, errno,
			"get_xattr_data: lgetxattr(%s,\"%s\",0) failed",
			full_fname(fname), name);
		return NULL;
	}

	if (!datum_len && !extra_len)
		extra_len = 1; /* request non-zero amount of memory */
	if (SIZE_MAX - datum_len < extra_len)
		overflow_exit("get_xattr_data");
	ptr = new_array(char, datum_len + extra_len);

	if (datum_len) {
		size_t len = sys_lgetxattr(fname, name, ptr, datum_len);
		if (len != datum_len) {
			if (len == (size_t)-1) {
				rsyserr(FERROR_XFER, errno,
					"get_xattr_data: lgetxattr(%s,\"%s\",%ld) failed",
					full_fname(fname), name, (long)datum_len);
			} else {
				rprintf(FERROR_XFER,
					"get_xattr_data: lgetxattr(%s,\"%s\",%ld) returned %ld\n",
					full_fname(fname), name,
					(long)datum_len, (long)len);
			}
			free(ptr);
			return NULL;
		}
	}

	return ptr;
}

static int rsync_xal_get(const char *fname, item_list *xalp)
{
	ssize_t list_len, name_len;
	size_t datum_len, name_offset;
	char *name, *ptr;
#ifdef HAVE_LINUX_XATTRS
	int user_only = am_sender ? 0 : !am_root;
#endif
	rsync_xa *rxa;
	int count;

	/* This puts the name list into the "namebuf" buffer. */
	if ((list_len = get_xattr_names(fname)) < 0)
		return -1;

	for (name = namebuf; list_len > 0; name += name_len) {
		name_len = strlen(name) + 1;
		list_len -= name_len;

		if (saw_xattr_filter) {
			if (name_is_excluded(name, NAME_IS_XATTR, ALL_FILTERS))
				continue;
		}
#ifdef HAVE_LINUX_XATTRS
		/* Choose between ignoring the system namespace or (non-root) ignoring any non-user namespace. */
		else if (user_only ? !HAS_PREFIX(name, USER_PREFIX) : HAS_PREFIX(name, SYSTEM_PREFIX))
			continue;
#endif

		/* No rsync.%FOO attributes are copied w/o 2 -X options. */
		if (name_len > RPRE_LEN && name[RPRE_LEN] == '%' && HAS_PREFIX(name, RSYNC_PREFIX)) {
			if ((am_sender && preserve_xattrs < 2)
			 || (am_root < 0
			  && (strcmp(name+RPRE_LEN+1, XSTAT_SUFFIX) == 0
			   || strcmp(name+RPRE_LEN+1, XACC_ACL_SUFFIX) == 0
			   || strcmp(name+RPRE_LEN+1, XDEF_ACL_SUFFIX) == 0)))
				continue;
		}

		datum_len = name_len; /* Pass extra size to get_xattr_data() */
		if (!(ptr = get_xattr_data(fname, name, &datum_len, 0)))
			return -1;

		if (datum_len > MAX_FULL_DATUM) {
			/* For large datums, we store a flag and a checksum. */
			name_offset = 1 + MAX_XATTR_DIGEST_LEN;
			sum_init(xattr_sum_nni, checksum_seed);
			sum_update(ptr, datum_len);
			free(ptr);

			ptr = new_array(char, name_offset + name_len);
			*ptr = XSTATE_ABBREV;
			sum_end(ptr + 1);
		} else
			name_offset = datum_len;

		rxa = EXPAND_ITEM_LIST(xalp, rsync_xa, RSYNC_XAL_INITIAL);
		rxa->name = ptr + name_offset;
		memcpy(rxa->name, name, name_len);
		rxa->datum = ptr;
		rxa->name_len = name_len;
		rxa->datum_len = datum_len;
	}
	count = xalp->count;
	rxa = xalp->items;
	if (count > 1)
		qsort(rxa, count, sizeof (rsync_xa), rsync_xal_compare_names);
	for (rxa += count-1; count; count--, rxa--)
		rxa->num = count;
	return 0;
}

/* Read the xattr(s) for this filename. */
int get_xattr(const char *fname, stat_x *sxp)
{
	sxp->xattr = new(item_list);
	*sxp->xattr = empty_xattr;

	if (S_ISREG(sxp->st.st_mode) || S_ISDIR(sxp->st.st_mode)) {
		/* Everyone supports this. */
	} else if (S_ISLNK(sxp->st.st_mode)) {
#ifndef NO_SYMLINK_XATTRS
		if (!preserve_links)
#endif
			return 0;
	} else if (IS_SPECIAL(sxp->st.st_mode)) {
#ifndef NO_SPECIAL_XATTRS
		if (!preserve_specials)
#endif
			return 0;
	} else if (IS_DEVICE(sxp->st.st_mode)) {
#ifndef NO_DEVICE_XATTRS
		if (!preserve_devices)
#endif
			return 0;
	} else if (IS_MISSING_FILE(sxp->st))
		return 0;

	if (rsync_xal_get(fname, sxp->xattr) < 0) {
		free_xattr(sxp);
		return -1;
	}
	return 0;
}

int copy_xattrs(const char *source, const char *dest)
{
	ssize_t list_len, name_len;
	size_t datum_len;
	char *name, *ptr;
#ifdef HAVE_LINUX_XATTRS
	int user_only = am_sender ? 0 : am_root <= 0;
#endif

	/* This puts the name list into the "namebuf" buffer. */
	if ((list_len = get_xattr_names(source)) < 0)
		return -1;

	for (name = namebuf; list_len > 0; name += name_len) {
		name_len = strlen(name) + 1;
		list_len -= name_len;

		if (saw_xattr_filter) {
			if (name_is_excluded(name, NAME_IS_XATTR, ALL_FILTERS))
				continue;
		}
#ifdef HAVE_LINUX_XATTRS
		/* Choose between ignoring the system namespace or (non-root) ignoring any non-user namespace. */
		else if (user_only ? !HAS_PREFIX(name, USER_PREFIX) : HAS_PREFIX(name, SYSTEM_PREFIX))
			continue;
#endif

		datum_len = 0;
		if (!(ptr = get_xattr_data(source, name, &datum_len, 0)))
			return -1;
		if (sys_lsetxattr(dest, name, ptr, datum_len) < 0) {
			int save_errno = errno ? errno : EINVAL;
			rsyserr(FERROR_XFER, errno,
				"copy_xattrs: lsetxattr(%s,\"%s\") failed",
				full_fname(dest), name);
			errno = save_errno;
			return -1;
		}
		free(ptr);
	}

	return 0;
}

static int64 xattr_lookup_hash(const item_list *xalp)
{
	const rsync_xa *rxas = xalp->items;
	size_t i;
	int64 key = hashlittle2(&xalp->count, sizeof xalp->count);

	for (i = 0; i < xalp->count; i++) {
		key += hashlittle2(rxas[i].name, rxas[i].name_len);
		if (rxas[i].datum_len > MAX_FULL_DATUM)
			key += hashlittle2(rxas[i].datum, xattr_sum_len);
		else
			key += hashlittle2(rxas[i].datum, rxas[i].datum_len);
	}

	return key;
}

static int find_matching_xattr(const item_list *xalp)
{
	const struct ht_int64_node *node;
	const rsync_xa_list_ref *ref;
	int64 key;

	if (rsync_xal_h == NULL)
		return -1;

	key = xattr_lookup_hash(xalp);

	node = hashtable_find(rsync_xal_h, key, NULL);
	if (node == NULL)
		return -1;

	if (node->data == NULL)
		return -1;

	for (ref = node->data; ref != NULL; ref = ref->next) {
		const rsync_xa_list *ptr = rsync_xal_l.items;
		const rsync_xa *rxas1;
		const rsync_xa *rxas2 = xalp->items;
		size_t j;

		ptr += ref->ndx;
		rxas1 = ptr->xa_items.items;

		/* Wrong number of elements? */
		if (ptr->xa_items.count != xalp->count)
			continue;
		/* any elements different? */
		for (j = 0; j < xalp->count; j++) {
			if (rxas1[j].name_len != rxas2[j].name_len
			 || rxas1[j].datum_len != rxas2[j].datum_len
			 || strcmp(rxas1[j].name, rxas2[j].name))
				break;
			if (rxas1[j].datum_len > MAX_FULL_DATUM) {
				if (memcmp(rxas1[j].datum + 1,
					   rxas2[j].datum + 1,
					   xattr_sum_len) != 0)
					break;
			} else {
				if (memcmp(rxas1[j].datum, rxas2[j].datum,
					   rxas2[j].datum_len))
					break;
			}
		}
		/* no differences found.  This is The One! */
		if (j == xalp->count)
			return ref->ndx;
	}

	return -1;
}

/* Store *xalp on the end of rsync_xal_l */
static int rsync_xal_store(item_list *xalp)
{
	struct ht_int64_node *node;
	int ndx = rsync_xal_l.count; /* pre-incremented count */
	rsync_xa_list *new_list = EXPAND_ITEM_LIST(&rsync_xal_l, rsync_xa_list, RSYNC_XAL_LIST_INITIAL);
	rsync_xa_list_ref *new_ref;
	/* Since the following call starts a new list, we know it will hold the
	 * entire initial-count, not just enough space for one new item. */
	*new_list = empty_xa_list;
	(void)EXPAND_ITEM_LIST(&new_list->xa_items, rsync_xa, xalp->count);
	memcpy(new_list->xa_items.items, xalp->items, xalp->count * sizeof (rsync_xa));
	new_list->xa_items.count = xalp->count;
	xalp->count = 0;

	new_list->ndx = ndx;
	new_list->key = xattr_lookup_hash(&new_list->xa_items);

	if (rsync_xal_h == NULL)
		rsync_xal_h = hashtable_create(512, HT_KEY64);

	new_ref = new0(rsync_xa_list_ref);
	new_ref->ndx = ndx;

	node = hashtable_find(rsync_xal_h, new_list->key, new_ref);
	if (node->data != (void*)new_ref) {
		rsync_xa_list_ref *ref = node->data;

		while (ref != NULL) {
			if (ref->next != NULL) {
				ref = ref->next;
				continue;
			}

			ref->next = new_ref;
			break;
		}
	}

	return ndx;
}

/* Send the make_xattr()-generated xattr list for this flist entry. */
int send_xattr(int f, stat_x *sxp)
{
	int ndx = find_matching_xattr(sxp->xattr);

	/* Send 0 (-1 + 1) to indicate that literal xattr data follows. */
	write_varint(f, ndx + 1);

	if (ndx < 0) {
		rsync_xa *rxa;
		int count = sxp->xattr->count;
		write_varint(f, count);
		for (rxa = sxp->xattr->items; count--; rxa++) {
			size_t name_len = rxa->name_len;
			const char *name = rxa->name;
			/* Strip the rsync prefix from disguised namespaces. */
			if (name_len > RPRE_LEN
#ifdef HAVE_LINUX_XATTRS
			 && am_root < 0
#endif
			 && name[RPRE_LEN] != '%' && HAS_PREFIX(name, RSYNC_PREFIX)) {
				name += RPRE_LEN;
				name_len -= RPRE_LEN;
			}
#ifndef HAVE_LINUX_XATTRS
			else {
				/* Put everything else in the user namespace. */
				name_len += UPRE_LEN;
			}
#endif
			write_varint(f, name_len);
			write_varint(f, rxa->datum_len);
#ifndef HAVE_LINUX_XATTRS
			if (name_len > rxa->name_len) {
				write_buf(f, USER_PREFIX, UPRE_LEN);
				name_len -= UPRE_LEN;
			}
#endif
			write_buf(f, name, name_len);
			if (rxa->datum_len > MAX_FULL_DATUM)
				write_buf(f, rxa->datum + 1, xattr_sum_len);
			else
				write_bigbuf(f, rxa->datum, rxa->datum_len);
		}
		ndx = rsync_xal_store(sxp->xattr); /* adds item to rsync_xal_l */
	}

	return ndx;
}

/* Return a flag indicating if we need to change a file's xattrs.  If
 * "find_all" is specified, also mark any abbreviated xattrs that we
 * need so that send_xattr_request() can tell the sender about them. */
int xattr_diff(struct file_struct *file, stat_x *sxp, int find_all)
{
	const rsync_xa_list *glst = rsync_xal_l.items;
	const item_list *lst;
	rsync_xa *snd_rxa, *rec_rxa;
	int snd_cnt, rec_cnt;
	int cmp, same, xattrs_equal = 1;

	if (sxp && XATTR_READY(*sxp)) {
		rec_rxa = sxp->xattr->items;
		rec_cnt = sxp->xattr->count;
	} else {
		rec_rxa = NULL;
		rec_cnt = 0;
	}

	if (F_XATTR(file) >= 0) {
		glst += F_XATTR(file);
		lst = &glst->xa_items;
	} else
		lst = &empty_xattr;

	snd_rxa = lst->items;
	snd_cnt = lst->count;

	/* If the count of the sender's xattrs is different from our
	 * (receiver's) xattrs, the lists are not the same. */
	if (snd_cnt != rec_cnt) {
		if (!find_all)
			return 1;
		xattrs_equal = 0;
	}

	while (snd_cnt) {
		cmp = rec_cnt ? strcmp(snd_rxa->name, rec_rxa->name) : -1;
		if (cmp > 0)
			same = 0;
		else if (snd_rxa->datum_len > MAX_FULL_DATUM) {
			same = cmp == 0 && snd_rxa->datum_len == rec_rxa->datum_len
			    && memcmp(snd_rxa->datum + 1, rec_rxa->datum + 1,
				      xattr_sum_len) == 0;
			/* Flag unrequested items that we need. */
			if (!same && find_all && snd_rxa->datum[0] == XSTATE_ABBREV)
				snd_rxa->datum[0] = XSTATE_TODO;
		} else {
			same = cmp == 0 && snd_rxa->datum_len == rec_rxa->datum_len
			    && memcmp(snd_rxa->datum, rec_rxa->datum,
				      snd_rxa->datum_len) == 0;
		}
		if (!same) {
			if (!find_all)
				return 1;
			xattrs_equal = 0;
		}

		if (cmp <= 0) {
			snd_rxa++;
			snd_cnt--;
		}
		if (cmp >= 0) {
			rec_rxa++;
			rec_cnt--;
		}
	}

	if (rec_cnt)
		xattrs_equal = 0;

	return !xattrs_equal;
}

/* When called by the generator (with a NULL fname), this tells the sender
 * all the abbreviated xattr values we need.  When called by the sender
 * (with a non-NULL fname), we send all the extra xattr data it needs.
 * The generator may also call with f_out < 0 to just change all the
 * XSTATE_ABBREV states into XSTATE_DONE. */
void send_xattr_request(const char *fname, struct file_struct *file, int f_out)
{
	const rsync_xa_list *glst = rsync_xal_l.items;
	const item_list *lst;
	int cnt, prior_req = 0;
	rsync_xa *rxa;

	glst += F_XATTR(file);
	lst = &glst->xa_items;

	for (rxa = lst->items, cnt = lst->count; cnt--; rxa++) {
		if (rxa->datum_len <= MAX_FULL_DATUM)
			continue;
		switch (rxa->datum[0]) {
		case XSTATE_ABBREV:
			/* Items left abbreviated matched the sender's checksum, so
			 * the receiver will cache the local data for future use. */
			if (am_generator)
				rxa->datum[0] = XSTATE_DONE;
			continue;
		case XSTATE_TODO:
			assert(f_out >= 0);
			break;
		default:
			continue;
		}

		/* Flag that we handled this abbreviated item. */
		rxa->datum[0] = XSTATE_DONE;

		write_varint(f_out, rxa->num - prior_req);
		prior_req = rxa->num;

		if (fname) {
			size_t len = 0;
			char *ptr;

			/* Re-read the long datum. */
			if (!(ptr = get_xattr_data(fname, rxa->name, &len, 0))) {
				rprintf(FERROR_XFER, "failed to re-read xattr %s for %s\n", rxa->name, fname);
				write_varint(f_out, 0);
				continue;
			}

			write_varint(f_out, len); /* length might have changed! */
			write_bigbuf(f_out, ptr, len);
			free(ptr);
		}
	}

	if (f_out >= 0)
		write_byte(f_out, 0); /* end the list */
}

/* When called by the sender, read the request from the generator and mark
 * any needed xattrs with a flag that lets us know they need to be sent to
 * the receiver.  When called by the receiver, reads the sent data and
 * stores it in place of its checksum. */
int recv_xattr_request(struct file_struct *file, int f_in)
{
	const rsync_xa_list *glst = rsync_xal_l.items;
	const item_list *lst;
	char *old_datum, *name;
	rsync_xa *rxa;
	int rel_pos, cnt, num, got_xattr_data = 0;

	if (F_XATTR(file) < 0) {
		rprintf(FERROR, "recv_xattr_request: internal data error!\n");
		exit_cleanup(RERR_PROTOCOL);
	}
	glst += F_XATTR(file);
	lst = &glst->xa_items;

	cnt = lst->count;
	rxa = lst->items;
	num = 0;
	while ((rel_pos = read_varint(f_in)) != 0) {
		num += rel_pos;
		if (am_sender) {
			/* The sender-related num values are only in order on the sender.
			 * We use that order here to scan forward or backward as needed. */
			if (rel_pos < 0) {
				while (cnt < (int)lst->count && rxa->num > num) {
					rxa--;
					cnt++;
				}
			} else {
				while (cnt > 1 && rxa->num < num) {
					rxa++;
					cnt--;
				}
			}
		} else {
			int j;
			/* The receiving side has no known num order, so we just scan
			 * forward (w/wrap) and hope that the next value is near by. */
			for (j = lst->count; j > 1 && rxa->num != num; j--) {
				if (--cnt)
					rxa++;
				else {
					cnt = lst->count;
					rxa = lst->items;
				}
			}
		}
		if (!cnt || rxa->num != num) {
			rprintf(FERROR, "[%s] could not find xattr #%d for %s\n",
				who_am_i(), num, f_name(file, NULL));
			exit_cleanup(RERR_PROTOCOL);
		}
		if (!XATTR_ABBREV(*rxa) || rxa->datum[0] != XSTATE_ABBREV) {
			rprintf(FERROR, "[%s] internal abbrev error on %s (%s, len=%ld)!\n",
				who_am_i(), f_name(file, NULL), rxa->name, (long)rxa->datum_len);
			exit_cleanup(RERR_PROTOCOL);
		}

		if (am_sender) {
			rxa->datum[0] = XSTATE_TODO;
			continue;
		}

		old_datum = rxa->datum;
		rxa->datum_len = read_varint(f_in);

		if (SIZE_MAX - rxa->name_len < rxa->datum_len)
			overflow_exit("recv_xattr_request");
		rxa->datum = new_array(char, rxa->datum_len + rxa->name_len);
		name = rxa->datum + rxa->datum_len;
		memcpy(name, rxa->name, rxa->name_len);
		rxa->name = name;
		free(old_datum);
		read_buf(f_in, rxa->datum, rxa->datum_len);
		got_xattr_data = 1;
	}

	return got_xattr_data;
}

/* ------------------------------------------------------------------------- */

/* receive and build the rsync_xattr_lists */
void receive_xattr(int f, struct file_struct *file)
{
	static item_list temp_xattr = EMPTY_ITEM_LIST;
	int count, num;
#ifdef HAVE_LINUX_XATTRS
	int need_sort = 0;
#else
	int need_sort = 1;
#endif
	int ndx = read_varint(f);

	if (ndx < 0 || (size_t)ndx > rsync_xal_l.count) {
		rprintf(FERROR, "receive_xattr: xa index %d out of"
			" range for %s\n", ndx, f_name(file, NULL));
		exit_cleanup(RERR_STREAMIO);
	}

	if (ndx != 0) {
		F_XATTR(file) = ndx - 1;
		return;
	}

	if ((count = read_varint(f)) != 0) {
		(void)EXPAND_ITEM_LIST(&temp_xattr, rsync_xa, count);
		temp_xattr.count = 0;
	}

	for (num = 1; num <= count; num++) {
		char *ptr, *name;
		rsync_xa *rxa;
		size_t name_len = read_varint(f);
		size_t datum_len = read_varint(f);
		size_t dget_len = datum_len > MAX_FULL_DATUM ? 1 + (size_t)xattr_sum_len : datum_len;
		size_t extra_len = MIGHT_NEED_RPRE ? RPRE_LEN : 0;
		if (SIZE_MAX - dget_len < extra_len || SIZE_MAX - dget_len - extra_len < name_len)
			overflow_exit("receive_xattr");
		ptr = new_array(char, dget_len + extra_len + name_len);
		name = ptr + dget_len + extra_len;
		read_buf(f, name, name_len);
		if (name_len < 1 || name[name_len-1] != '\0') {
			rprintf(FERROR, "Invalid xattr name received (missing trailing \\0).\n");
			exit_cleanup(RERR_FILEIO);
		}
		if (dget_len == datum_len)
			read_buf(f, ptr, dget_len);
		else {
			*ptr = XSTATE_ABBREV;
			read_buf(f, ptr + 1, xattr_sum_len);
		}

		if (saw_xattr_filter) {
			if (name_is_excluded(name, NAME_IS_XATTR, ALL_FILTERS)) {
				free(ptr);
				continue;
			}
		}
#ifdef HAVE_LINUX_XATTRS
		/* Non-root can only save the user namespace. */
		if (am_root <= 0 && !HAS_PREFIX(name, USER_PREFIX)) {
			if (!am_root && !saw_xattr_filter) {
				free(ptr);
				continue;
			}
			name -= RPRE_LEN;
			name_len += RPRE_LEN;
			memcpy(name, RSYNC_PREFIX, RPRE_LEN);
			need_sort = 1;
		}
#else
		/* This OS only has a user namespace, so we either
		 * strip the user prefix, or we put a non-user
		 * namespace inside our rsync hierarchy. */
		if (HAS_PREFIX(name, USER_PREFIX)) {
			name += UPRE_LEN;
			name_len -= UPRE_LEN;
		} else if (am_root) {
			name -= RPRE_LEN;
			name_len += RPRE_LEN;
			memcpy(name, RSYNC_PREFIX, RPRE_LEN);
		} else {
			free(ptr);
			continue;
		}
#endif
		/* No rsync.%FOO attributes are copied w/o 2 -X options. */
		if (preserve_xattrs < 2 && name_len > RPRE_LEN
		 && name[RPRE_LEN] == '%' && HAS_PREFIX(name, RSYNC_PREFIX)) {
			free(ptr);
			continue;
		}

		rxa = EXPAND_ITEM_LIST(&temp_xattr, rsync_xa, 1);
		rxa->name = name;
		rxa->datum = ptr;
		rxa->name_len = name_len;
		rxa->datum_len = datum_len;
		rxa->num = num;
	}

	if (need_sort && count > 1)
		qsort(temp_xattr.items, count, sizeof (rsync_xa), rsync_xal_compare_names);

	ndx = rsync_xal_store(&temp_xattr); /* adds item to rsync_xal_l */

	F_XATTR(file) = ndx;
}

/* Turn the xattr data in stat_x into cached xattr data, setting the index
 * values in the file struct. */
void cache_tmp_xattr(struct file_struct *file, stat_x *sxp)
{
	int ndx;

	if (!sxp->xattr)
		return;

	if (prior_xattr_count == (size_t)-1)
		prior_xattr_count = rsync_xal_l.count;
	ndx = find_matching_xattr(sxp->xattr);
	if (ndx < 0)
		rsync_xal_store(sxp->xattr); /* adds item to rsync_xal_l */

	F_XATTR(file) = ndx;
}

void uncache_tmp_xattrs(void)
{
	if (prior_xattr_count != (size_t)-1) {
		rsync_xa_list *xa_list_item = rsync_xal_l.items;
		rsync_xa_list *xa_list_start = xa_list_item + prior_xattr_count;
		xa_list_item += rsync_xal_l.count;
		rsync_xal_l.count = prior_xattr_count;
		while (xa_list_item-- > xa_list_start) {
			struct ht_int64_node *node;
			rsync_xa_list_ref *ref;

			rsync_xal_free(&xa_list_item->xa_items);

			if (rsync_xal_h == NULL)
				continue;

			node = hashtable_find(rsync_xal_h, xa_list_item->key, NULL);
			if (node == NULL)
				continue;

			if (node->data == NULL)
				continue;

			ref = node->data;
			if (xa_list_item->ndx == ref->ndx) {
				/* xa_list_item is the first in the list. */
				node->data = ref->next;
				free(ref);
				continue;
			}

			while (1) {
				rsync_xa_list_ref *next = ref->next;
				if (next == NULL)
					break;
				if (xa_list_item->ndx == next->ndx) {
					ref->next = next->next;
					free(next);
					break;
				}
				ref = next;
			}
		}
		prior_xattr_count = (size_t)-1;
	}
}

static int rsync_xal_set(const char *fname, item_list *xalp,
			 const char *fnamecmp, stat_x *sxp)
{
	rsync_xa *rxas = xalp->items;
	ssize_t list_len;
	size_t i, len;
	char *name, *ptr, sum[MAX_XATTR_DIGEST_LEN];
#ifdef HAVE_LINUX_XATTRS
	int user_only = am_root <= 0;
#endif
	size_t name_len;
	int ret = 0;

	/* This puts the current name list into the "namebuf" buffer. */
	if ((list_len = get_xattr_names(fname)) < 0)
		return -1;

	for (i = 0; i < xalp->count; i++) {
		name = rxas[i].name;

		if (XATTR_ABBREV(rxas[i])) {
			/* See if the fnamecmp version is identical. */
			len = name_len = rxas[i].name_len;
			if ((ptr = get_xattr_data(fnamecmp, name, &len, 1)) == NULL) {
			  still_abbrev:
				if (am_generator)
					continue;
				rprintf(FERROR, "Missing abbreviated xattr value, %s, for %s\n",
					rxas[i].name, full_fname(fname));
				ret = -1;
				continue;
			}
			if (len != rxas[i].datum_len) {
				free(ptr);
				goto still_abbrev;
			}

			sum_init(xattr_sum_nni, checksum_seed);
			sum_update(ptr, len);
			sum_end(sum);
			if (memcmp(sum, rxas[i].datum + 1, xattr_sum_len) != 0) {
				free(ptr);
				goto still_abbrev;
			}

			if (fname == fnamecmp)
				; /* Value is already set when identical */
			else if (sys_lsetxattr(fname, name, ptr, len) < 0) {
				rsyserr(FERROR_XFER, errno,
					"rsync_xal_set: lsetxattr(%s,\"%s\") failed",
					full_fname(fname), name);
				ret = -1;
			} else /* make sure caller sets mtime */
				sxp->st.st_mtime = (time_t)-1;

			if (am_generator) { /* generator items stay abbreviated */
				free(ptr);
				continue;
			}

			memcpy(ptr + len, name, name_len);
			free(rxas[i].datum);

			rxas[i].name = name = ptr + len;
			rxas[i].datum = ptr;
			continue;
		}

		if (sys_lsetxattr(fname, name, rxas[i].datum, rxas[i].datum_len) < 0) {
			rsyserr(FERROR_XFER, errno,
				"rsync_xal_set: lsetxattr(%s,\"%s\") failed",
				full_fname(fname), name);
			ret = -1;
		} else /* make sure caller sets mtime */
			sxp->st.st_mtime = (time_t)-1;
	}

	/* Remove any extraneous names. */
	for (name = namebuf; list_len > 0; name += name_len) {
		name_len = strlen(name) + 1;
		list_len -= name_len;

		if (saw_xattr_filter) {
			if (name_is_excluded(name, NAME_IS_XATTR, ALL_FILTERS))
				continue;
		}
#ifdef HAVE_LINUX_XATTRS
		/* Choose between ignoring the system namespace or (non-root) ignoring any non-user namespace. */
		else if (user_only ? !HAS_PREFIX(name, USER_PREFIX) : HAS_PREFIX(name, SYSTEM_PREFIX))
			continue;
#endif
		if (am_root < 0 && name_len > RPRE_LEN && name[RPRE_LEN] == '%' && strcmp(name, XSTAT_ATTR) == 0)
			continue;

		for (i = 0; i < xalp->count; i++) {
			if (strcmp(name, rxas[i].name) == 0)
				break;
		}
		if (i == xalp->count) {
			if (sys_lremovexattr(fname, name) < 0) {
				rsyserr(FERROR_XFER, errno,
					"rsync_xal_set: lremovexattr(%s,\"%s\") failed",
					full_fname(fname), name);
				ret = -1;
			} else /* make sure caller sets mtime */
				sxp->st.st_mtime = (time_t)-1;
		}
	}

	return ret;
}

/* Set extended attributes on indicated filename. */
int set_xattr(const char *fname, const struct file_struct *file, const char *fnamecmp, stat_x *sxp)
{
	rsync_xa_list *glst = rsync_xal_l.items;
	item_list *lst;
	int ndx, added_write_perm = 0;

	if (dry_run)
		return 1; /* FIXME: --dry-run needs to compute this value */

	if (read_only || list_only) {
		errno = EROFS;
		return -1;
	}

#ifdef NO_SPECIAL_XATTRS
	if (IS_SPECIAL(sxp->st.st_mode)) {
		errno = ENOTSUP;
		return -1;
	}
#endif
#ifdef NO_DEVICE_XATTRS
	if (IS_DEVICE(sxp->st.st_mode)) {
		errno = ENOTSUP;
		return -1;
	}
#endif
#ifdef NO_SYMLINK_XATTRS
	if (S_ISLNK(sxp->st.st_mode)) {
		errno = ENOTSUP;
		return -1;
	}
#endif

	/* If the target file lacks write permission, we try to add it
	 * temporarily so we can change the extended attributes. */
	if (!am_root
#ifdef SUPPORT_LINKS
	 && !S_ISLNK(sxp->st.st_mode)
#endif
	 && access(fname, W_OK) < 0
	 && do_chmod(fname, (sxp->st.st_mode & CHMOD_BITS) | S_IWUSR) == 0)
		added_write_perm = 1;

	ndx = F_XATTR(file);
	glst += ndx;
	lst = &glst->xa_items;
	int return_value = rsync_xal_set(fname, lst, fnamecmp, sxp);
	if (added_write_perm) /* remove the temporary write permission */
		do_chmod(fname, sxp->st.st_mode);
	return return_value;
}

#ifdef SUPPORT_ACLS
char *get_xattr_acl(const char *fname, int is_access_acl, size_t *len_p)
{
	const char *name = is_access_acl ? XACC_ACL_ATTR : XDEF_ACL_ATTR;
	*len_p = 0; /* no extra data alloc needed from get_xattr_data() */
	return get_xattr_data(fname, name, len_p, 1);
}

int set_xattr_acl(const char *fname, int is_access_acl, const char *buf, size_t buf_len)
{
	const char *name = is_access_acl ? XACC_ACL_ATTR : XDEF_ACL_ATTR;
	if (sys_lsetxattr(fname, name, buf, buf_len) < 0) {
		rsyserr(FERROR_XFER, errno,
			"set_xattr_acl: lsetxattr(%s,\"%s\") failed",
			full_fname(fname), name);
		return -1;
	}
	return 0;
}

int del_def_xattr_acl(const char *fname)
{
	return sys_lremovexattr(fname, XDEF_ACL_ATTR);
}
#endif

int get_stat_xattr(const char *fname, int fd, STRUCT_STAT *fst, STRUCT_STAT *xst)
{
	unsigned int mode;
	int rdev_major, rdev_minor, uid, gid, len;
	char buf[256];

	if (am_root >= 0 || IS_DEVICE(fst->st_mode) || IS_SPECIAL(fst->st_mode))
		return -1;

	if (xst)
		*xst = *fst;
	else
		xst = fst;
	if (fname) {
		fd = -1;
		len = sys_lgetxattr(fname, XSTAT_ATTR, buf, sizeof buf - 1);
	} else {
		fname = "fd";
		len = sys_fgetxattr(fd, XSTAT_ATTR, buf, sizeof buf - 1);
	}
	if (len >= (int)sizeof buf) {
		len = -1;
		errno = ERANGE;
	}
	if (len < 0) {
		if (errno == ENOTSUP || errno == ENOATTR)
			return -1;
		if (errno == EPERM && S_ISLNK(fst->st_mode)) {
			xst->st_uid = 0;
			xst->st_gid = 0;
			return 0;
		}
		rsyserr(FERROR_XFER, errno, "failed to read xattr %s for %s",
			XSTAT_ATTR, full_fname(fname));
		return -1;
	}
	buf[len] = '\0';

	if (sscanf(buf, "%o %d,%d %d:%d",
		   &mode, &rdev_major, &rdev_minor, &uid, &gid) != 5) {
		rprintf(FERROR, "Corrupt %s xattr attached to %s: \"%s\"\n",
			XSTAT_ATTR, full_fname(fname), buf);
		exit_cleanup(RERR_FILEIO);
	}

	xst->st_mode = from_wire_mode(mode);
	xst->st_rdev = MAKEDEV(rdev_major, rdev_minor);
	xst->st_uid = uid;
	xst->st_gid = gid;

	return 0;
}

int set_stat_xattr(const char *fname, struct file_struct *file, mode_t new_mode)
{
	STRUCT_STAT fst, xst;
	dev_t rdev;
	mode_t mode, fmode;

	if (dry_run)
		return 0;

	if (read_only || list_only) {
		rsyserr(FERROR_XFER, EROFS, "failed to write xattr %s for %s",
			XSTAT_ATTR, full_fname(fname));
		return -1;
	}

	if (x_lstat(fname, &fst, &xst) < 0) {
		rsyserr(FERROR_XFER, errno, "failed to re-stat %s",
			full_fname(fname));
		return -1;
	}

	fst.st_mode &= (_S_IFMT | CHMOD_BITS);
	fmode = new_mode & (_S_IFMT | CHMOD_BITS);

	if (IS_DEVICE(fmode)) {
		uint32 *devp = F_RDEV_P(file);
		rdev = MAKEDEV(DEV_MAJOR(devp), DEV_MINOR(devp));
	} else
		rdev = 0;

	/* Dump the special permissions and enable full owner access. */
	mode = (fst.st_mode & _S_IFMT) | (fmode & ACCESSPERMS)
	     | (S_ISDIR(fst.st_mode) ? 0700 : 0600);
	if (fst.st_mode != mode)
		do_chmod(fname, mode);
	if (!IS_DEVICE(fst.st_mode))
		fst.st_rdev = 0; /* just in case */

	if (mode == fmode && fst.st_rdev == rdev
	 && fst.st_uid == F_OWNER(file) && fst.st_gid == F_GROUP(file)) {
		/* xst.st_mode will be 0 if there's no current stat xattr */
		if (xst.st_mode && sys_lremovexattr(fname, XSTAT_ATTR) < 0) {
			rsyserr(FERROR_XFER, errno,
				"delete of stat xattr failed for %s",
				full_fname(fname));
			return -1;
		}
		return 0;
	}

	if (xst.st_mode != fmode || xst.st_rdev != rdev
	 || xst.st_uid != F_OWNER(file) || xst.st_gid != F_GROUP(file)) {
		char buf[256];
		int len = snprintf(buf, sizeof buf, "%o %u,%u %u:%u",
			to_wire_mode(fmode),
			(int)major(rdev), (int)minor(rdev),
			F_OWNER(file), F_GROUP(file));
		if (sys_lsetxattr(fname, XSTAT_ATTR, buf, len) < 0) {
			if (errno == EPERM && S_ISLNK(fst.st_mode))
				return 0;
			rsyserr(FERROR_XFER, errno,
				"failed to write xattr %s for %s",
				XSTAT_ATTR, full_fname(fname));
			return -1;
		}
	}

	return 0;
}

int x_stat(const char *fname, STRUCT_STAT *fst, STRUCT_STAT *xst)
{
	int ret = do_stat(fname, fst);
	if ((ret < 0 || get_stat_xattr(fname, -1, fst, xst) < 0) && xst)
		xst->st_mode = 0;
	return ret;
}

int x_lstat(const char *fname, STRUCT_STAT *fst, STRUCT_STAT *xst)
{
	int ret = do_lstat(fname, fst);
	if ((ret < 0 || get_stat_xattr(fname, -1, fst, xst) < 0) && xst)
		xst->st_mode = 0;
	return ret;
}

int x_fstat(int fd, STRUCT_STAT *fst, STRUCT_STAT *xst)
{
	int ret = do_fstat(fd, fst);
	if ((ret < 0 || get_stat_xattr(NULL, fd, fst, xst) < 0) && xst)
		xst->st_mode = 0;
	return ret;
}

#endif /* SUPPORT_XATTRS */
