xref: /openbsd-src/usr.bin/rsync/blocks.c (revision 7eb67ff9a029ded5e7c98bba369a471c1bd0f5da)
1*7eb67ff9Stb /*	$OpenBSD: blocks.c,v 1.27 2024/09/27 13:13:14 tb Exp $ */
260a32ee9Sbenno /*
360a32ee9Sbenno  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
460a32ee9Sbenno  *
560a32ee9Sbenno  * Permission to use, copy, modify, and distribute this software for any
660a32ee9Sbenno  * purpose with or without fee is hereby granted, provided that the above
760a32ee9Sbenno  * copyright notice and this permission notice appear in all copies.
860a32ee9Sbenno  *
960a32ee9Sbenno  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1060a32ee9Sbenno  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1160a32ee9Sbenno  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1260a32ee9Sbenno  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1360a32ee9Sbenno  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1460a32ee9Sbenno  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1560a32ee9Sbenno  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1660a32ee9Sbenno  */
17ea555712Sflorian #include <sys/queue.h>
1860a32ee9Sbenno #include <sys/stat.h>
1960a32ee9Sbenno 
2060a32ee9Sbenno #include <assert.h>
2160a32ee9Sbenno #include <endian.h>
2260a32ee9Sbenno #include <errno.h>
2360a32ee9Sbenno #include <inttypes.h>
2460a32ee9Sbenno #include <stdio.h>
2560a32ee9Sbenno #include <stdlib.h>
2660a32ee9Sbenno #include <string.h>
2760a32ee9Sbenno #include <unistd.h>
2860a32ee9Sbenno 
2934b470cbStb #include <openssl/md4.h>
3034b470cbStb 
3160a32ee9Sbenno #include "extern.h"
3260a32ee9Sbenno 
33ea555712Sflorian struct	blkhash {
34ea555712Sflorian 	const struct blk	*blk;
35ea555712Sflorian 	TAILQ_ENTRY(blkhash)	 entries;
36ea555712Sflorian };
37ea555712Sflorian 
38ea555712Sflorian TAILQ_HEAD(blkhashq, blkhash);
39ea555712Sflorian 
40ea555712Sflorian /*
41ea555712Sflorian  * Table used by the sender for looking up blocks.
42ea555712Sflorian  * The blocks are those sent by the receiver; we're looking up using
43ea555712Sflorian  * hashes computed from our local file.
44ea555712Sflorian  */
45ea555712Sflorian struct	blktab {
46ea555712Sflorian 	struct blkhashq	*q; /* entries in the hashtable */
47ea555712Sflorian 	size_t		 qsz; /* size of the hashtable */
48ea555712Sflorian 	struct blkhash	*blks; /* pre-allocated hashtable entries */
49ea555712Sflorian };
50ea555712Sflorian 
51ea555712Sflorian /*
52ea555712Sflorian  * This is the number of buckets in the hashtable.
53ea555712Sflorian  * Use the same that GPL rsync uses.
54ea555712Sflorian  * (It should be dynamic?)
55ea555712Sflorian  */
56ea555712Sflorian #define	BLKTAB_SZ	  65536
57ea555712Sflorian 
58ea555712Sflorian /*
59ea555712Sflorian  * Initialise an empty hashtable with BLKTAB_SZ entries in it.
60ea555712Sflorian  * Populate it for each file with blkhash_set.
61ea555712Sflorian  * When we've processed all files, call blkhash_free.
62ea555712Sflorian  * Returns NULL on allocation failure.
63ea555712Sflorian  */
64ea555712Sflorian struct blktab *
65ea555712Sflorian blkhash_alloc(void)
66ea555712Sflorian {
67ea555712Sflorian 	struct blktab	*p;
68ea555712Sflorian 
69ea555712Sflorian 	if ((p = calloc(1, sizeof(struct blktab))) == NULL) {
70ea555712Sflorian 		ERR("calloc");
71ea555712Sflorian 		return NULL;
72ea555712Sflorian 	}
73ea555712Sflorian 	p->qsz = BLKTAB_SZ;
74ea555712Sflorian 	p->q = calloc(p->qsz, sizeof(struct blkhashq));
75ea555712Sflorian 	if (p->q == NULL) {
76ea555712Sflorian 		ERR("calloc");
77ea555712Sflorian 		free(p);
78ea555712Sflorian 		return NULL;
79ea555712Sflorian 	}
80ea555712Sflorian 	return p;
81ea555712Sflorian }
82ea555712Sflorian 
83ea555712Sflorian /*
84ea555712Sflorian  * Populate the hashtable with an incoming file's blocks.
85ea555712Sflorian  * This will clear out any existing hashed data.
86ea555712Sflorian  * Returns zero on allocation failure, non-zero otherwise.
87ea555712Sflorian  */
88ea555712Sflorian int
89ea555712Sflorian blkhash_set(struct blktab *p, const struct blkset *bset)
90ea555712Sflorian {
91cd54cfb8Stb 	struct blkhash	*blks;
92ea555712Sflorian 	size_t		 i, idx;
93ea555712Sflorian 
9454ac4772Stb 	if (bset == NULL || bset->blksz == 0)
95ea555712Sflorian 		return 1;
96ea555712Sflorian 
97ea555712Sflorian 	/* Wipe clean the table. */
98ea555712Sflorian 
99ea555712Sflorian 	for (i = 0; i < p->qsz; i++)
100ea555712Sflorian 		TAILQ_INIT(&p->q[i]);
101ea555712Sflorian 
102ea555712Sflorian 	/* Fill in the hashtable. */
103ea555712Sflorian 
104cd54cfb8Stb 	blks = reallocarray(p->blks, bset->blksz, sizeof(struct blkhash));
105cd54cfb8Stb 	if (blks == NULL) {
106ea555712Sflorian 		ERR("reallocarray");
107cd54cfb8Stb 		free(p->blks);
108cd54cfb8Stb 		p->blks = NULL;
109ea555712Sflorian 		return 0;
110ea555712Sflorian 	}
111cd54cfb8Stb 	p->blks = blks;
112ea555712Sflorian 	for (i = 0; i < bset->blksz; i++) {
113ea555712Sflorian 		p->blks[i].blk = &bset->blks[i];
114ea555712Sflorian 		idx = bset->blks[i].chksum_short % p->qsz;
115ea555712Sflorian 		assert(idx < p->qsz);
116ea555712Sflorian 		TAILQ_INSERT_TAIL(&p->q[idx], &p->blks[i], entries);
117ea555712Sflorian 	}
118ea555712Sflorian 
119ea555712Sflorian 	return 1;
120ea555712Sflorian }
121ea555712Sflorian 
122ea555712Sflorian /*
123ea555712Sflorian  * Free as allocated with blkhash_alloc().
124ea555712Sflorian  */
125ea555712Sflorian void
126ea555712Sflorian blkhash_free(struct blktab *p)
127ea555712Sflorian {
128*7eb67ff9Stb 	if (p == NULL)
129*7eb67ff9Stb 		return;
130a1cea692Sjob 	free(p->q);
131ea555712Sflorian 	free(p->blks);
132ea555712Sflorian 	free(p);
133ea555712Sflorian }
134ea555712Sflorian 
13560a32ee9Sbenno /*
13660a32ee9Sbenno  * From our current position of "offs" in buffer "buf" of total size
13760a32ee9Sbenno  * "size", see if we can find a matching block in our list of blocks.
13860a32ee9Sbenno  * The "hint" refers to the block that *might* work.
13960a32ee9Sbenno  * Returns the blk or NULL if no matching block was found.
14060a32ee9Sbenno  */
141ea555712Sflorian static const struct blk *
142ea555712Sflorian blk_find(struct sess *sess, struct blkstat *st,
143ea555712Sflorian 	const struct blkset *blks, const char *path, int recomp)
14460a32ee9Sbenno {
14560a32ee9Sbenno 	unsigned char	 md[MD4_DIGEST_LENGTH];
14660a32ee9Sbenno 	uint32_t	 fhash;
14760a32ee9Sbenno 	off_t		 remain, osz;
14860a32ee9Sbenno 	int		 have_md = 0;
149ea555712Sflorian 	char		*map;
150ea555712Sflorian 	const struct blkhashq *q;
151ea555712Sflorian 	const struct blkhash *ent;
152ea555712Sflorian 
153ea555712Sflorian 	remain = st->mapsz - st->offs;
154ea555712Sflorian 	assert(remain);
155ea555712Sflorian 	osz = MINIMUM(remain, (off_t)blks->len);
15660a32ee9Sbenno 
15760a32ee9Sbenno 	/*
158ea555712Sflorian 	 * First, compute our fast hash the hard way (if we're
159ea555712Sflorian 	 * reentering this function from a previous block match, or the
160ea555712Sflorian 	 * first time) or from our existing s1 and s2 values.
16160a32ee9Sbenno 	 */
16260a32ee9Sbenno 
163ea555712Sflorian 	if (!recomp) {
164ea555712Sflorian 		fhash = (st->s1 & 0xFFFF) | (st->s2 << 16);
165ea555712Sflorian 	} else {
166ea555712Sflorian 		fhash = hash_fast(st->map + st->offs, (size_t)osz);
167ea555712Sflorian 		st->s1 = fhash & 0xFFFF;
168ea555712Sflorian 		st->s2 = fhash >> 16;
169ea555712Sflorian 	}
17060a32ee9Sbenno 
17160a32ee9Sbenno 	/*
17260a32ee9Sbenno 	 * Start with our match hint.
17360a32ee9Sbenno 	 * This just runs the fast and slow check with the hint.
17460a32ee9Sbenno 	 */
17560a32ee9Sbenno 
176ea555712Sflorian 	if (st->hint < blks->blksz &&
177ea555712Sflorian 	    fhash == blks->blks[st->hint].chksum_short &&
178ea555712Sflorian 	    (size_t)osz == blks->blks[st->hint].len) {
179ea555712Sflorian 		hash_slow(st->map + st->offs, (size_t)osz, md, sess);
18060a32ee9Sbenno 		have_md = 1;
181ea555712Sflorian 		if (memcmp(md, blks->blks[st->hint].chksum_long, blks->csum) == 0) {
182b2a7eac7Sbenno 			LOG4("%s: found matching hinted match: "
18302f20df6Sderaadt 			    "position %jd, block %zu (position %jd, size %zu)",
18402f20df6Sderaadt 			    path,
185ea555712Sflorian 			    (intmax_t)st->offs, blks->blks[st->hint].idx,
186ea555712Sflorian 			    (intmax_t)blks->blks[st->hint].offs,
187ea555712Sflorian 			    blks->blks[st->hint].len);
188ea555712Sflorian 			return &blks->blks[st->hint];
18960a32ee9Sbenno 		}
19060a32ee9Sbenno 	}
19160a32ee9Sbenno 
19260a32ee9Sbenno 	/*
193ea555712Sflorian 	 * Look for the fast hash modulus in our hashtable, filter for
194ea555712Sflorian 	 * those matching the full hash and length, then move to the
195ea555712Sflorian 	 * slow hash.
196ea555712Sflorian 	 * The slow hash is computed only once.
19760a32ee9Sbenno 	 */
19860a32ee9Sbenno 
199ea555712Sflorian 	q = &st->blktab->q[fhash % st->blktab->qsz];
200ea555712Sflorian 
201ea555712Sflorian 	TAILQ_FOREACH(ent, q, entries) {
202ea555712Sflorian 		if (fhash != ent->blk->chksum_short ||
203ea555712Sflorian 		    (size_t)osz != ent->blk->len)
20460a32ee9Sbenno 			continue;
20560a32ee9Sbenno 
206b2a7eac7Sbenno 		LOG4("%s: found matching fast match: "
20702f20df6Sderaadt 		    "position %jd, block %zu (position %jd, size %zu)",
208ea555712Sflorian 		    path, (intmax_t)st->offs, ent->blk->idx,
209ea555712Sflorian 		    (intmax_t)ent->blk->offs, ent->blk->len);
21060a32ee9Sbenno 
211f1dcb30aSderaadt 		if (have_md == 0) {
212ea555712Sflorian 			hash_slow(st->map + st->offs, (size_t)osz, md, sess);
21360a32ee9Sbenno 			have_md = 1;
21460a32ee9Sbenno 		}
21560a32ee9Sbenno 
216ea555712Sflorian 		if (memcmp(md, ent->blk->chksum_long, blks->csum))
21760a32ee9Sbenno 			continue;
21860a32ee9Sbenno 
219b2a7eac7Sbenno 		LOG4("%s: sender verifies slow match", path);
220ea555712Sflorian 		return ent->blk;
221ea555712Sflorian 	}
222ea555712Sflorian 
223ea555712Sflorian 	/*
224ea555712Sflorian 	 * Adjust our partial sums for the hashing.
225ea555712Sflorian 	 * We first remove the first byte from the sum.
226ea555712Sflorian 	 * We then, if we have space, add the first byte of the next
227ea555712Sflorian 	 * block in the sequence.
228ea555712Sflorian 	 */
229ea555712Sflorian 
230ea555712Sflorian 	map = st->map + st->offs;
231ea555712Sflorian 	st->s1 -= map[0];
232ea555712Sflorian 	st->s2 -= osz * map[0];
233ea555712Sflorian 
234ea555712Sflorian 	if (osz < remain) {
235ea555712Sflorian 		st->s1 += map[osz];
236ea555712Sflorian 		st->s2 += st->s1;
23760a32ee9Sbenno 	}
23860a32ee9Sbenno 
23960a32ee9Sbenno 	return NULL;
24060a32ee9Sbenno }
24160a32ee9Sbenno 
24260a32ee9Sbenno /*
24364a7cfb7Sflorian  * Given a local file "path" and the blocks created by a remote machine,
24464a7cfb7Sflorian  * find out which blocks of our file they don't have and send them.
24564a7cfb7Sflorian  * This function is reentrant: it must be called while there's still
24664a7cfb7Sflorian  * data to send.
24760a32ee9Sbenno  */
24864a7cfb7Sflorian void
24964a7cfb7Sflorian blk_match(struct sess *sess, const struct blkset *blks,
25064a7cfb7Sflorian 	const char *path, struct blkstat *st)
25160a32ee9Sbenno {
252042f4afaSclaudio 	off_t		  last, end = 0, sz;
25360a32ee9Sbenno 	int32_t		  tok;
254ea555712Sflorian 	size_t		  i;
255ea555712Sflorian 	const struct blk *blk;
25660a32ee9Sbenno 
25760a32ee9Sbenno 	/*
25864a7cfb7Sflorian 	 * If the file's empty or we don't have any blocks from the
25964a7cfb7Sflorian 	 * sender, then simply send the whole file.
26064a7cfb7Sflorian 	 * Otherwise, run the hash matching routine and send raw chunks
26164a7cfb7Sflorian 	 * and subsequent matching tokens.
26264a7cfb7Sflorian 	 */
26364a7cfb7Sflorian 
26464a7cfb7Sflorian 	if (st->mapsz && blks->blksz) {
26564a7cfb7Sflorian 		/*
26664a7cfb7Sflorian 		 * Stop searching at the length of the file minus the
26764a7cfb7Sflorian 		 * size of the last block.
26860a32ee9Sbenno 		 * The reason for this being that we don't need to do an
26964a7cfb7Sflorian 		 * incremental hash within the last block---if it
27064a7cfb7Sflorian 		 * doesn't match, it doesn't match.
27160a32ee9Sbenno 		 */
27260a32ee9Sbenno 
2730889042fSflorian 		end = st->mapsz + 1 - blks->blks[blks->blksz - 1].len;
274042f4afaSclaudio 	}
27560a32ee9Sbenno 
276042f4afaSclaudio 	last = st->offs;
277ea555712Sflorian 	for (i = 0; st->offs < end; st->offs++, i++) {
278ea555712Sflorian 		blk = blk_find(sess, st, blks, path, i == 0);
279f1dcb30aSderaadt 		if (blk == NULL)
28060a32ee9Sbenno 			continue;
28160a32ee9Sbenno 
2820889042fSflorian 		sz = st->offs - last;
2830889042fSflorian 		st->dirty += sz;
2840889042fSflorian 		st->total += sz;
285b2a7eac7Sbenno 		LOG4("%s: flushing %jd B before %zu B block %zu",
28602f20df6Sderaadt 		    path, (intmax_t)sz,
28764a7cfb7Sflorian 		    blk->len, blk->idx);
28860a32ee9Sbenno 		tok = -(blk->idx + 1);
28960a32ee9Sbenno 
290dcad62ecSclaudio 		hash_file_buf(&st->ctx, st->map + last, sz + blk->len);
291dcad62ecSclaudio 
29260a32ee9Sbenno 		/*
29364a7cfb7Sflorian 		 * Write the data we have, then follow it with
29464a7cfb7Sflorian 		 * the tag of the block that matches.
29560a32ee9Sbenno 		 */
29660a32ee9Sbenno 
29764a7cfb7Sflorian 		st->curpos = last;
29864a7cfb7Sflorian 		st->curlen = st->curpos + sz;
29964a7cfb7Sflorian 		st->curtok = tok;
300ed5cc9fbSderaadt 		assert(st->curtok != 0);
30164a7cfb7Sflorian 		st->curst = sz ? BLKSTAT_DATA : BLKSTAT_TOK;
3020889042fSflorian 		st->total += blk->len;
3030889042fSflorian 		st->offs += blk->len;
3040889042fSflorian 		st->hint = blk->idx + 1;
305dcad62ecSclaudio 
30664a7cfb7Sflorian 		return;
30760a32ee9Sbenno 	}
30860a32ee9Sbenno 
30960a32ee9Sbenno 	/* Emit remaining data and send terminator token. */
31060a32ee9Sbenno 
3110889042fSflorian 	sz = st->mapsz - last;
312042f4afaSclaudio 	LOG4("%s: flushing %s %jd B", path,
313042f4afaSclaudio 	    last == 0 ? "whole" : "remaining", (intmax_t)sz);
314042f4afaSclaudio 
315042f4afaSclaudio 	hash_file_buf(&st->ctx, st->map + last, sz);
31664a7cfb7Sflorian 
3170889042fSflorian 	st->total += sz;
3180889042fSflorian 	st->dirty += sz;
31964a7cfb7Sflorian 	st->curpos = last;
32064a7cfb7Sflorian 	st->curlen = st->curpos + sz;
32164a7cfb7Sflorian 	st->curtok = 0;
32264a7cfb7Sflorian 	st->curst = sz ? BLKSTAT_DATA : BLKSTAT_TOK;
32360a32ee9Sbenno }
32460a32ee9Sbenno 
32560a32ee9Sbenno /*
32655cb9f91Sbenno  * Buffer the message from sender to the receiver indicating that the
32755cb9f91Sbenno  * block set has been received.
32860a32ee9Sbenno  * Symmetrises blk_send_ack().
32960a32ee9Sbenno  */
33055cb9f91Sbenno void
331ba617adaSbenno blk_recv_ack(char buf[20], const struct blkset *blocks, int32_t idx)
33260a32ee9Sbenno {
333ceac33e0Sflorian 	size_t	 pos = 0, sz;
33460a32ee9Sbenno 
335ceac33e0Sflorian 	sz = sizeof(int32_t) + /* index */
336ceac33e0Sflorian 	    sizeof(int32_t) + /* block count */
337ceac33e0Sflorian 	    sizeof(int32_t) + /* block length */
338ceac33e0Sflorian 	    sizeof(int32_t) + /* checksum length */
339ceac33e0Sflorian 	    sizeof(int32_t); /* block remainder */
34055cb9f91Sbenno 	assert(sz == 20);
34160a32ee9Sbenno 
342ba617adaSbenno 	io_buffer_int(buf, &pos, sz, idx);
343ba617adaSbenno 	io_buffer_int(buf, &pos, sz, blocks->blksz);
344ba617adaSbenno 	io_buffer_int(buf, &pos, sz, blocks->len);
345ba617adaSbenno 	io_buffer_int(buf, &pos, sz, blocks->csum);
346ba617adaSbenno 	io_buffer_int(buf, &pos, sz, blocks->rem);
347ceac33e0Sflorian 	assert(pos == sz);
348ceac33e0Sflorian }
349ceac33e0Sflorian 
35060a32ee9Sbenno /*
35160a32ee9Sbenno  * Read all of the checksums for a file's blocks.
35260a32ee9Sbenno  * Returns the set of blocks or NULL on failure.
35360a32ee9Sbenno  */
35460a32ee9Sbenno struct blkset *
35560a32ee9Sbenno blk_recv(struct sess *sess, int fd, const char *path)
35660a32ee9Sbenno {
35760a32ee9Sbenno 	struct blkset	*s;
35860a32ee9Sbenno 	int32_t		 i;
35960a32ee9Sbenno 	size_t		 j;
36060a32ee9Sbenno 	struct blk	*b;
36160a32ee9Sbenno 	off_t		 offs = 0;
36260a32ee9Sbenno 
363f1dcb30aSderaadt 	if ((s = calloc(1, sizeof(struct blkset))) == NULL) {
364b2a7eac7Sbenno 		ERR("calloc");
36560a32ee9Sbenno 		return NULL;
36660a32ee9Sbenno 	}
36760a32ee9Sbenno 
36860a32ee9Sbenno 	/*
36960a32ee9Sbenno 	 * The block prologue consists of a few values that we'll need
37060a32ee9Sbenno 	 * in reading the individual blocks for this file.
37160a32ee9Sbenno 	 * FIXME: read into buffer and unbuffer.
37260a32ee9Sbenno 	 */
37360a32ee9Sbenno 
37460a32ee9Sbenno 	if (!io_read_size(sess, fd, &s->blksz)) {
375b2a7eac7Sbenno 		ERRX1("io_read_size");
37660a32ee9Sbenno 		goto out;
37760a32ee9Sbenno 	} else if (!io_read_size(sess, fd, &s->len)) {
378b2a7eac7Sbenno 		ERRX1("io_read_size");
37960a32ee9Sbenno 		goto out;
38060a32ee9Sbenno 	} else if (!io_read_size(sess, fd, &s->csum)) {
381b2a7eac7Sbenno 		ERRX1("io_read_int");
38260a32ee9Sbenno 		goto out;
38360a32ee9Sbenno 	} else if (!io_read_size(sess, fd, &s->rem)) {
384b2a7eac7Sbenno 		ERRX1("io_read_int");
38560a32ee9Sbenno 		goto out;
38660a32ee9Sbenno 	} else if (s->rem && s->rem >= s->len) {
387b2a7eac7Sbenno 		ERRX("block remainder is "
38860a32ee9Sbenno 			"greater than block size");
38960a32ee9Sbenno 		goto out;
39060a32ee9Sbenno 	}
39160a32ee9Sbenno 
392b2a7eac7Sbenno 	LOG3("%s: read block prologue: %zu blocks of "
39360a32ee9Sbenno 	    "%zu B, %zu B remainder, %zu B checksum", path,
39460a32ee9Sbenno 	    s->blksz, s->len, s->rem, s->csum);
39560a32ee9Sbenno 
39660a32ee9Sbenno 	if (s->blksz) {
39760a32ee9Sbenno 		s->blks = calloc(s->blksz, sizeof(struct blk));
398f1dcb30aSderaadt 		if (s->blks == NULL) {
399b2a7eac7Sbenno 			ERR("calloc");
40060a32ee9Sbenno 			goto out;
40160a32ee9Sbenno 		}
40260a32ee9Sbenno 	}
40360a32ee9Sbenno 
40460a32ee9Sbenno 	/*
40560a32ee9Sbenno 	 * Read each block individually.
40660a32ee9Sbenno 	 * FIXME: read buffer and unbuffer.
40760a32ee9Sbenno 	 */
40860a32ee9Sbenno 
40960a32ee9Sbenno 	for (j = 0; j < s->blksz; j++) {
41060a32ee9Sbenno 		b = &s->blks[j];
41160a32ee9Sbenno 		if (!io_read_int(sess, fd, &i)) {
412b2a7eac7Sbenno 			ERRX1("io_read_int");
41360a32ee9Sbenno 			goto out;
41460a32ee9Sbenno 		}
41560a32ee9Sbenno 		b->chksum_short = i;
41660a32ee9Sbenno 
41760a32ee9Sbenno 		assert(s->csum <= sizeof(b->chksum_long));
41860a32ee9Sbenno 		if (!io_read_buf(sess,
41960a32ee9Sbenno 		    fd, b->chksum_long, s->csum)) {
420b2a7eac7Sbenno 			ERRX1("io_read_buf");
42160a32ee9Sbenno 			goto out;
42260a32ee9Sbenno 		}
42360a32ee9Sbenno 
42460a32ee9Sbenno 		/*
42560a32ee9Sbenno 		 * If we're the last block, then we're assigned the
42660a32ee9Sbenno 		 * remainder of the data.
42760a32ee9Sbenno 		 */
42860a32ee9Sbenno 
42960a32ee9Sbenno 		b->offs = offs;
43060a32ee9Sbenno 		b->idx = j;
43160a32ee9Sbenno 		b->len = (j == (s->blksz - 1) && s->rem) ?
43260a32ee9Sbenno 			s->rem : s->len;
43360a32ee9Sbenno 		offs += b->len;
43460a32ee9Sbenno 
435b2a7eac7Sbenno 		LOG4("%s: read block %zu, length %zu B",
43602f20df6Sderaadt 		    path, b->idx, b->len);
43760a32ee9Sbenno 	}
43860a32ee9Sbenno 
43960a32ee9Sbenno 	s->size = offs;
440b2a7eac7Sbenno 	LOG3("%s: read blocks: %zu blocks, %jd B total blocked data",
44102f20df6Sderaadt 	    path, s->blksz, (intmax_t)s->size);
44260a32ee9Sbenno 	return s;
44360a32ee9Sbenno out:
4440889042fSflorian 	free(s->blks);
4450889042fSflorian 	free(s);
44660a32ee9Sbenno 	return NULL;
44760a32ee9Sbenno }
44860a32ee9Sbenno 
44960a32ee9Sbenno /*
45060a32ee9Sbenno  * Symmetrise blk_recv_ack(), except w/o the leading identifier.
45160a32ee9Sbenno  * Return zero on failure, non-zero on success.
45260a32ee9Sbenno  */
45360a32ee9Sbenno int
45460a32ee9Sbenno blk_send_ack(struct sess *sess, int fd, struct blkset *p)
45560a32ee9Sbenno {
45660a32ee9Sbenno 	char	 buf[16];
45760a32ee9Sbenno 	size_t	 pos = 0, sz;
45860a32ee9Sbenno 
45960a32ee9Sbenno 	/* Put the entire send routine into a buffer. */
46060a32ee9Sbenno 
46160a32ee9Sbenno 	sz = sizeof(int32_t) + /* block count */
46260a32ee9Sbenno 	    sizeof(int32_t) + /* block length */
46360a32ee9Sbenno 	    sizeof(int32_t) + /* checksum length */
46460a32ee9Sbenno 	    sizeof(int32_t); /* block remainder */
46560a32ee9Sbenno 	assert(sz <= sizeof(buf));
46660a32ee9Sbenno 
46760a32ee9Sbenno 	if (!io_read_buf(sess, fd, buf, sz)) {
468b2a7eac7Sbenno 		ERRX1("io_read_buf");
46960a32ee9Sbenno 		return 0;
47060a32ee9Sbenno 	}
47160a32ee9Sbenno 
472ba617adaSbenno 	if (!io_unbuffer_size(buf, &pos, sz, &p->blksz))
473b2a7eac7Sbenno 		ERRX1("io_unbuffer_size");
474ba617adaSbenno 	else if (!io_unbuffer_size(buf, &pos, sz, &p->len))
475b2a7eac7Sbenno 		ERRX1("io_unbuffer_size");
476ba617adaSbenno 	else if (!io_unbuffer_size(buf, &pos, sz, &p->csum))
477b2a7eac7Sbenno 		ERRX1("io_unbuffer_size");
478ba617adaSbenno 	else if (!io_unbuffer_size(buf, &pos, sz, &p->rem))
479b2a7eac7Sbenno 		ERRX1("io_unbuffer_size");
48060a32ee9Sbenno 	else if (p->len && p->rem >= p->len)
481b2a7eac7Sbenno 		ERRX1("non-zero length is less than remainder");
482f1dcb30aSderaadt 	else if (p->csum == 0 || p->csum > 16)
483b2a7eac7Sbenno 		ERRX1("inappropriate checksum length");
48460a32ee9Sbenno 	else
48560a32ee9Sbenno 		return 1;
48660a32ee9Sbenno 
48760a32ee9Sbenno 	return 0;
48860a32ee9Sbenno }
48960a32ee9Sbenno 
49060a32ee9Sbenno /*
49160a32ee9Sbenno  * Transmit the metadata for set and blocks.
49260a32ee9Sbenno  * Return zero on failure, non-zero on success.
49360a32ee9Sbenno  */
49460a32ee9Sbenno int
49560a32ee9Sbenno blk_send(struct sess *sess, int fd, size_t idx,
49660a32ee9Sbenno 	const struct blkset *p, const char *path)
49760a32ee9Sbenno {
49860a32ee9Sbenno 	char	*buf;
49960a32ee9Sbenno 	size_t	 i, pos = 0, sz;
50060a32ee9Sbenno 	int	 rc = 0;
50160a32ee9Sbenno 
50260a32ee9Sbenno 	/* Put the entire send routine into a buffer. */
50360a32ee9Sbenno 
50460a32ee9Sbenno 	sz = sizeof(int32_t) + /* identifier */
50560a32ee9Sbenno 	    sizeof(int32_t) + /* block count */
50660a32ee9Sbenno 	    sizeof(int32_t) + /* block length */
50760a32ee9Sbenno 	    sizeof(int32_t) + /* checksum length */
50860a32ee9Sbenno 	    sizeof(int32_t) + /* block remainder */
50960a32ee9Sbenno 	    p->blksz *
51060a32ee9Sbenno 	    (sizeof(int32_t) + /* short checksum */
51160a32ee9Sbenno 		p->csum); /* long checksum */
51260a32ee9Sbenno 
513f1dcb30aSderaadt 	if ((buf = malloc(sz)) == NULL) {
514b2a7eac7Sbenno 		ERR("malloc");
51560a32ee9Sbenno 		return 0;
51660a32ee9Sbenno 	}
51760a32ee9Sbenno 
518ba617adaSbenno 	io_buffer_int(buf, &pos, sz, idx);
519ba617adaSbenno 	io_buffer_int(buf, &pos, sz, p->blksz);
520ba617adaSbenno 	io_buffer_int(buf, &pos, sz, p->len);
521ba617adaSbenno 	io_buffer_int(buf, &pos, sz, p->csum);
522ba617adaSbenno 	io_buffer_int(buf, &pos, sz, p->rem);
52360a32ee9Sbenno 
52460a32ee9Sbenno 	for (i = 0; i < p->blksz; i++) {
525dc4a3938Sderaadt 		io_buffer_int(buf, &pos, sz, p->blks[i].chksum_short);
526ba617adaSbenno 		io_buffer_buf(buf, &pos, sz, p->blks[i].chksum_long, p->csum);
52760a32ee9Sbenno 	}
52860a32ee9Sbenno 
52960a32ee9Sbenno 	assert(pos == sz);
53060a32ee9Sbenno 
53160a32ee9Sbenno 	if (!io_write_buf(sess, fd, buf, sz)) {
532b2a7eac7Sbenno 		ERRX1("io_write_buf");
53360a32ee9Sbenno 		goto out;
53460a32ee9Sbenno 	}
53560a32ee9Sbenno 
536b2a7eac7Sbenno 	LOG3("%s: sent block prologue: %zu blocks of %zu B, "
53702f20df6Sderaadt 	    "%zu B remainder, %zu B checksum",
53802f20df6Sderaadt 	    path, p->blksz, p->len, p->rem, p->csum);
53960a32ee9Sbenno 	rc = 1;
54060a32ee9Sbenno out:
54160a32ee9Sbenno 	free(buf);
54260a32ee9Sbenno 	return rc;
54360a32ee9Sbenno }
544