[BACK]Return to sender.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / rsync

File: [local] / src / usr.bin / rsync / sender.c (download)

Revision 1.4, Mon Feb 11 19:18:36 2019 UTC (5 years, 3 months ago) by deraadt
Branch: MAIN
Changes since 1.3: +18 -18 lines

cleanup weird spaces around !.  (We normalize source-code to a standard
idiom because it is less error prone for a larger team.  kristaps idiom
is highly divergent)
ok benno

/*	$Id: sender.c,v 1.4 2019/02/11 19:18:36 deraadt Exp $ */
/*
 * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include <sys/stat.h>

#include <assert.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "extern.h"

/*
 * A client sender manages the read-only source files and sends data to
 * the receiver as requested.
 * First it sends its list of files, then it waits for the server to
 * request updates to individual files.
 * Returns zero on failure, non-zero on success.
 *
 * Pledges: stdio, rpath, unveil.
 */
int
rsync_sender(struct sess *sess, int fdin,
	int fdout, size_t argc, char **argv)
{
	struct flist	*fl = NULL;
	size_t		 flsz = 0, phase = 0, excl;
	int		 rc = 0, c;
	int32_t		 idx;
	struct blkset	*blks = NULL;

	if (-1 == pledge("stdio rpath unveil", NULL)) {
		ERR(sess, "pledge");
		return 0;
	}

	/*
	 * Generate the list of files we want to send from our
	 * command-line input.
	 * This will also remove all invalid files.
	 */

	if (!flist_gen(sess, argc, argv, &fl, &flsz)) {
		ERRX1(sess, "flist_gen");
		goto out;
	}

	/* Client sends zero-length exclusions if deleting. */

	if (!sess->opts->server && sess->opts->del &&
	     !io_write_int(sess, fdout, 0)) {
		ERRX1(sess, "io_write_int");
		goto out;
	}

	/*
	 * Then the file list in any mode.
	 * Finally, the IO error (always zero for us).
	 */

	if (!flist_send(sess, fdin, fdout, fl, flsz)) {
		ERRX1(sess, "flist_send");
		goto out;
	} else if (!io_write_int(sess, fdout, 0)) {
		ERRX1(sess, "io_write_int");
		goto out;
	}

	/* Exit if we're the server with zero files. */

	if (0 == flsz && sess->opts->server) {
		WARNX(sess, "sender has empty file list: exiting");
		rc = 1;
		goto out;
	} else if (!sess->opts->server)
		LOG1(sess, "Transfer starting: %zu files", flsz);

	/*
	 * If we're the server, read our exclusion list.
	 * This is always 0 for now.
	 */

	if (sess->opts->server) {
		if (!io_read_size(sess, fdin, &excl)) {
			ERRX1(sess, "io_read_size");
			goto out;
		} else if (0 != excl) {
			ERRX1(sess, "exclusion list is non-empty");
			goto out;
		}
	}

	/*
	 * We have two phases: the first has a two-byte checksum, the
	 * second has a full 16-byte checksum.
	 */

	LOG2(sess, "sender transmitting phase 1 data");

	for (;;) {
		if (!io_read_int(sess, fdin, &idx)) {
			ERRX1(sess, "io_read_int");
			goto out;
		}

		/*
		 * If we receive an invalid index (-1), then we're
		 * either promoted to the second phase or it's time to
		 * exit, depending upon which phase we're in.
		 */

		if (-1 == idx) {
			if (!io_write_int(sess, fdout, idx)) {
				ERRX1(sess, "io_write_int");
				goto out;
			}

			/* FIXME: I don't understand this ack. */

			if (sess->opts->server && sess->rver > 27)
				if (!io_write_int(sess, fdout, idx)) {
					ERRX1(sess, "io_write_int");
					goto out;
				}

			if (phase++)
				break;
			LOG2(sess, "sender transmitting phase 2 data");
			continue;
		}

		/* Validate index and file type. */

		if (idx < 0 || (uint32_t)idx >= flsz) {
			ERRX(sess, "file index out of bounds: "
				"invalid %" PRId32 " out of %zu",
				idx, flsz);
			goto out;
		} else if (S_ISDIR(fl[idx].st.mode)) {
			ERRX(sess, "blocks requested for "
				"directory: %s", fl[idx].path);
			goto out;
		} else if (S_ISLNK(fl[idx].st.mode)) {
			ERRX(sess, "blocks requested for "
				"symlink: %s", fl[idx].path);
			goto out;
		} else if (!S_ISREG(fl[idx].st.mode)) {
			ERRX(sess, "blocks requested for "
				"special: %s", fl[idx].path);
			goto out;
		}

		if (!sess->opts->server)
			LOG1(sess, "%s", fl[idx].wpath);

		/* Dry-run doesn't do anything. */

		if (sess->opts->dry_run) {
			if (!io_write_int(sess, fdout, idx)) {
				ERRX1(sess, "io_write_int");
				goto out;
			}
			continue;
		}

		/*
		 * The server will now send us its view of the file.
		 * It does so by cutting a file into a series of blocks
		 * and checksumming each block.
		 * We can then compare the blocks in our file and those
		 * in theirs, and send them blocks they're missing or
		 * don't have.
		 */

		blks = blk_recv(sess, fdin, fl[idx].path);
		if (NULL == blks) {
			ERRX1(sess, "blk_recv");
			goto out;
		} else if (!blk_recv_ack(sess, fdout, blks, idx)) {
			ERRX1(sess, "blk_recv_ack");
			goto out;
		}

		c = blk_match(sess, fdout, blks, fl[idx].path);
		blkset_free(blks);

		if (!c) {
			ERRX1(sess, "blk_match");
			goto out;
		}
	}

	if (!sess_stats_send(sess, fdout)) {
		ERRX1(sess, "sess_stats_end");
		goto out;
	}

	/* Final "goodbye" message. */

	if (!io_read_int(sess, fdin, &idx)) {
		ERRX1(sess, "io_read_int");
		goto out;
	} else if (-1 != idx) {
		ERRX(sess, "read incorrect update complete ack");
		goto out;
	}

	LOG2(sess, "sender finished updating");
	rc = 1;
out:
	flist_free(fl, flsz);
	return rc;
}