Annotation of src/usr.bin/rsync/blocks.c, Revision 1.1
1.1 ! benno 1: /* $Id$ */
! 2: /*
! 3: * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
! 4: *
! 5: * Permission to use, copy, modify, and distribute this software for any
! 6: * purpose with or without fee is hereby granted, provided that the above
! 7: * copyright notice and this permission notice appear in all copies.
! 8: *
! 9: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
! 10: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
! 11: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
! 12: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
! 13: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
! 14: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
! 15: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
! 16: */
! 17: #include <sys/mman.h>
! 18: #include <sys/stat.h>
! 19:
! 20: #include <assert.h>
! 21: #include <endian.h>
! 22: #include <errno.h>
! 23: #include <fcntl.h>
! 24: #include <inttypes.h>
! 25: #include <stdio.h>
! 26: #include <stdlib.h>
! 27: #include <string.h>
! 28: #include <unistd.h>
! 29:
! 30: #include "md4.h"
! 31: #include "extern.h"
! 32:
! 33: /*
! 34: * Flush out "size" bytes of the buffer, doing all of the appropriate
! 35: * chunking of the data, then the subsequent token (or zero).
! 36: * This is symmetrised in blk_merge().
! 37: * Return zero on failure, non-zero on success.
! 38: */
! 39: static int
! 40: blk_flush(struct sess *sess, int fd,
! 41: const void *b, off_t size, int32_t token)
! 42: {
! 43: off_t i = 0, sz;
! 44:
! 45: while (i < size) {
! 46: sz = MAX_CHUNK < (size - i) ?
! 47: MAX_CHUNK : (size - i);
! 48: if ( ! io_write_int(sess, fd, sz)) {
! 49: ERRX1(sess, "io_write_int");
! 50: return 0;
! 51: } else if ( ! io_write_buf(sess, fd, b + i, sz)) {
! 52: ERRX1(sess, "io_write_buf");
! 53: return 0;
! 54: }
! 55: i += sz;
! 56: }
! 57:
! 58: if ( ! io_write_int(sess, fd, token)) {
! 59: ERRX1(sess, "io_write_int");
! 60: return 0;
! 61: }
! 62:
! 63: return 1;
! 64: }
! 65:
! 66: /*
! 67: * From our current position of "offs" in buffer "buf" of total size
! 68: * "size", see if we can find a matching block in our list of blocks.
! 69: * The "hint" refers to the block that *might* work.
! 70: * Returns the blk or NULL if no matching block was found.
! 71: */
! 72: static struct blk *
! 73: blk_find(struct sess *sess, const void *buf, off_t size, off_t offs,
! 74: const struct blkset *blks, const char *path, size_t hint)
! 75: {
! 76: unsigned char md[MD4_DIGEST_LENGTH];
! 77: uint32_t fhash;
! 78: off_t remain, osz;
! 79: size_t i;
! 80: int have_md = 0;
! 81:
! 82: /*
! 83: * First, compute our fast hash.
! 84: * FIXME: yes, this can be a rolling computation, but I'm
! 85: * deliberately making it simple first.
! 86: */
! 87:
! 88: remain = size - offs;
! 89: assert(remain);
! 90: osz = remain < (off_t)blks->len ? remain : (off_t)blks->len;
! 91: fhash = hash_fast(buf + offs, (size_t)osz);
! 92: have_md = 0;
! 93:
! 94: /*
! 95: * Start with our match hint.
! 96: * This just runs the fast and slow check with the hint.
! 97: */
! 98:
! 99: if (hint < blks->blksz &&
! 100: fhash == blks->blks[hint].chksum_short &&
! 101: (size_t)osz == blks->blks[hint].len) {
! 102: hash_slow(buf + offs, (size_t)osz, md, sess);
! 103: have_md = 1;
! 104: if (0 == memcmp(md,
! 105: blks->blks[hint].chksum_long, blks->csum)) {
! 106: LOG4(sess, "%s: found matching hinted match: "
! 107: "position %jd, block %zu "
! 108: "(position %jd, size %zu)", path,
! 109: (intmax_t)offs, blks->blks[hint].idx,
! 110: (intmax_t)blks->blks[hint].offs,
! 111: blks->blks[hint].len);
! 112: return &blks->blks[hint];
! 113: }
! 114: }
! 115:
! 116: /*
! 117: * Now loop and look for the fast hash.
! 118: * If it's found, move on to the slow hash.
! 119: */
! 120:
! 121: for (i = 0; i < blks->blksz; i++) {
! 122: if (fhash != blks->blks[i].chksum_short)
! 123: continue;
! 124: if ((size_t)osz != blks->blks[i].len)
! 125: continue;
! 126:
! 127: LOG4(sess, "%s: found matching fast match: "
! 128: "position %jd, block %zu "
! 129: "(position %jd, size %zu)", path,
! 130: (intmax_t)offs, blks->blks[i].idx,
! 131: (intmax_t)blks->blks[i].offs,
! 132: blks->blks[i].len);
! 133:
! 134: /* Compute slow hash on demand. */
! 135:
! 136: if (0 == have_md) {
! 137: hash_slow(buf + offs, (size_t)osz, md, sess);
! 138: have_md = 1;
! 139: }
! 140:
! 141: if (memcmp(md, blks->blks[i].chksum_long, blks->csum))
! 142: continue;
! 143:
! 144: LOG4(sess, "%s: sender verifies slow match", path);
! 145: return &blks->blks[i];
! 146: }
! 147:
! 148: return NULL;
! 149: }
! 150:
! 151: /*
! 152: * The main reconstruction algorithm on the sender side.
! 153: * Scans byte-wise over the input file, looking for matching blocks in
! 154: * what the server sent us.
! 155: * If a block is found, emit all data up until the block, then the token
! 156: * for the block.
! 157: * The receiving end can then reconstruct the file trivially.
! 158: * Return zero on failure, non-zero on success.
! 159: */
! 160: static int
! 161: blk_match_send(struct sess *sess, const char *path, int fd,
! 162: const void *buf, off_t size, const struct blkset *blks)
! 163: {
! 164: off_t offs, last, end, fromcopy = 0, fromdown = 0,
! 165: total = 0, sz;
! 166: int32_t tok;
! 167: struct blk *blk;
! 168: size_t hint = 0;
! 169:
! 170: /*
! 171: * Stop searching at the length of the file minus the size of
! 172: * the last block.
! 173: * The reason for this being that we don't need to do an
! 174: * incremental hash within the last block---if it doesn't match,
! 175: * it doesn't match.
! 176: */
! 177:
! 178: end = size + 1 - blks->blks[blks->blksz - 1].len;
! 179:
! 180: for (last = offs = 0; offs < end; offs++) {
! 181: blk = blk_find(sess, buf, size,
! 182: offs, blks, path, hint);
! 183: if (NULL == blk)
! 184: continue;
! 185:
! 186: sz = offs - last;
! 187: fromdown += sz;
! 188: total += sz;
! 189: LOG4(sess, "%s: flushing %jd B before %zu B "
! 190: "block %zu", path, (intmax_t)sz, blk->len,
! 191: blk->idx);
! 192: tok = -(blk->idx + 1);
! 193:
! 194: /*
! 195: * Write the data we have, then follow it with the tag
! 196: * of the block that matches.
! 197: * The receiver will then write our data, then the data
! 198: * it already has in the matching block.
! 199: */
! 200:
! 201: if ( ! blk_flush(sess, fd, buf + last, sz, tok)) {
! 202: ERRX1(sess, "blk_flush");
! 203: return 0;
! 204: }
! 205:
! 206: fromcopy += blk->len;
! 207: total += blk->len;
! 208: offs += blk->len - 1;
! 209: last = offs + 1;
! 210: hint = blk->idx + 1;
! 211: }
! 212:
! 213: /* Emit remaining data and send terminator token. */
! 214:
! 215: sz = size - last;
! 216: total += sz;
! 217: fromdown += sz;
! 218:
! 219: LOG4(sess, "%s: flushing remaining %jd B", path, (intmax_t)sz);
! 220:
! 221: if ( ! blk_flush(sess, fd, buf + last, sz, 0)) {
! 222: ERRX1(sess, "blk_flush");
! 223: return 0;
! 224: }
! 225:
! 226: LOG3(sess, "%s: flushed (chunked) %jd B total, "
! 227: "%.2f%% upload ratio", path, (intmax_t)total,
! 228: 100.0 * fromdown / total);
! 229: return 1;
! 230: }
! 231:
! 232: /*
! 233: * Given a local file "path" and the blocks created by a remote machine,
! 234: * find out which blocks of our file they don't have and send them.
! 235: * Return zero on failure, non-zero on success.
! 236: */
! 237: int
! 238: blk_match(struct sess *sess, int fd,
! 239: const struct blkset *blks, const char *path)
! 240: {
! 241: int nfd, rc = 0, c;
! 242: struct stat st;
! 243: void *map = MAP_FAILED;
! 244: size_t mapsz;
! 245: unsigned char filemd[MD4_DIGEST_LENGTH];
! 246:
! 247: /* Start by mapping our file into memory. */
! 248:
! 249: if (-1 == (nfd = open(path, O_RDONLY, 0))) {
! 250: ERR(sess, "%s: open", path);
! 251: return 0;
! 252: } else if (-1 == fstat(nfd, &st)) {
! 253: ERR(sess, "%s: fstat", path);
! 254: close(nfd);
! 255: return 0;
! 256: }
! 257:
! 258: /*
! 259: * We might possibly have a zero-length file, in which case the
! 260: * mmap() will fail, so only do this with non-zero files.
! 261: */
! 262:
! 263: if ((mapsz = st.st_size) > 0) {
! 264: map = mmap(NULL, mapsz, PROT_READ, MAP_SHARED, nfd, 0);
! 265: if (MAP_FAILED == map) {
! 266: ERR(sess, "%s: mmap", path);
! 267: close(nfd);
! 268: return 0;
! 269: }
! 270: }
! 271:
! 272: /*
! 273: * If the file's empty or we don't have any blocks from the
! 274: * sender, then simply send the whole file.
! 275: * Otherwise, run the hash matching routine and send raw chunks
! 276: * and subsequent matching tokens.
! 277: * This part broadly symmetrises blk_merge().
! 278: */
! 279:
! 280: if (st.st_size && blks->blksz) {
! 281: c = blk_match_send(sess, path,
! 282: fd, map, st.st_size, blks);
! 283: if ( ! c) {
! 284: ERRX1(sess, "blk_match_send");
! 285: goto out;
! 286: }
! 287: } else {
! 288: if ( ! blk_flush(sess, fd, map, st.st_size, 0)) {
! 289: ERRX1(sess, "blk_flush");
! 290: return 0;
! 291: }
! 292: LOG3(sess, "%s: flushed (un-chunked) %jd B, 100%% "
! 293: "upload ratio", path, (intmax_t)st.st_size);
! 294: }
! 295:
! 296: /*
! 297: * Now write the full file hash.
! 298: * Since we're seeding the hash, this always gives us some sort
! 299: * of data even if the file's zero-length.
! 300: */
! 301:
! 302: hash_file(map, st.st_size, filemd, sess);
! 303:
! 304: if ( ! io_write_buf(sess, fd, filemd, MD4_DIGEST_LENGTH)) {
! 305: ERRX1(sess, "io_write_buf");
! 306: goto out;
! 307: }
! 308:
! 309: rc = 1;
! 310: out:
! 311: if (MAP_FAILED != map)
! 312: munmap(map, mapsz);
! 313: close(nfd);
! 314: return rc;
! 315: }
! 316:
! 317: /* FIXME: remove. */
! 318: void
! 319: blkset_free(struct blkset *p)
! 320: {
! 321:
! 322: if (NULL == p)
! 323: return;
! 324: free(p->blks);
! 325: free(p);
! 326: }
! 327:
! 328: /*
! 329: * Sent from the sender to the receiver to indicate that the block set
! 330: * has been received.
! 331: * Symmetrises blk_send_ack().
! 332: * Returns zero on failure, non-zero on success.
! 333: */
! 334: int
! 335: blk_recv_ack(struct sess *sess,
! 336: int fd, const struct blkset *blocks, int32_t idx)
! 337: {
! 338:
! 339: /* FIXME: put into static block. */
! 340:
! 341: if ( ! io_write_int(sess, fd, idx))
! 342: ERRX1(sess, "io_write_int");
! 343: else if ( ! io_write_int(sess, fd, blocks->blksz))
! 344: ERRX1(sess, "io_write_int");
! 345: else if ( ! io_write_int(sess, fd, blocks->len))
! 346: ERRX1(sess, "io_write_int");
! 347: else if ( ! io_write_int(sess, fd, blocks->csum))
! 348: ERRX1(sess, "io_write_int");
! 349: else if ( ! io_write_int(sess, fd, blocks->rem))
! 350: ERRX1(sess, "io_write_int");
! 351: else
! 352: return 1;
! 353:
! 354: return 0;
! 355: }
! 356:
! 357: /*
! 358: * Read all of the checksums for a file's blocks.
! 359: * Returns the set of blocks or NULL on failure.
! 360: */
! 361: struct blkset *
! 362: blk_recv(struct sess *sess, int fd, const char *path)
! 363: {
! 364: struct blkset *s;
! 365: int32_t i;
! 366: size_t j;
! 367: struct blk *b;
! 368: off_t offs = 0;
! 369:
! 370: if (NULL == (s = calloc(1, sizeof(struct blkset)))) {
! 371: ERR(sess, "calloc");
! 372: return NULL;
! 373: }
! 374:
! 375: /*
! 376: * The block prologue consists of a few values that we'll need
! 377: * in reading the individual blocks for this file.
! 378: * FIXME: read into buffer and unbuffer.
! 379: */
! 380:
! 381: if ( ! io_read_size(sess, fd, &s->blksz)) {
! 382: ERRX1(sess, "io_read_size");
! 383: goto out;
! 384: } else if ( ! io_read_size(sess, fd, &s->len)) {
! 385: ERRX1(sess, "io_read_size");
! 386: goto out;
! 387: } else if ( ! io_read_size(sess, fd, &s->csum)) {
! 388: ERRX1(sess, "io_read_int");
! 389: goto out;
! 390: } else if ( ! io_read_size(sess, fd, &s->rem)) {
! 391: ERRX1(sess, "io_read_int");
! 392: goto out;
! 393: } else if (s->rem && s->rem >= s->len) {
! 394: ERRX(sess, "block remainder is "
! 395: "greater than block size");
! 396: goto out;
! 397: }
! 398:
! 399: LOG3(sess, "%s: read block prologue: %zu blocks of "
! 400: "%zu B, %zu B remainder, %zu B checksum", path,
! 401: s->blksz, s->len, s->rem, s->csum);
! 402:
! 403: if (s->blksz) {
! 404: s->blks = calloc(s->blksz, sizeof(struct blk));
! 405: if (NULL == s->blks) {
! 406: ERR(sess, "calloc");
! 407: goto out;
! 408: }
! 409: }
! 410:
! 411: /*
! 412: * Read each block individually.
! 413: * FIXME: read buffer and unbuffer.
! 414: */
! 415:
! 416: for (j = 0; j < s->blksz; j++) {
! 417: b = &s->blks[j];
! 418: if ( ! io_read_int(sess, fd, &i)) {
! 419: ERRX1(sess, "io_read_int");
! 420: goto out;
! 421: }
! 422: b->chksum_short = i;
! 423:
! 424: assert(s->csum <= sizeof(b->chksum_long));
! 425: if ( ! io_read_buf(sess,
! 426: fd, b->chksum_long, s->csum)) {
! 427: ERRX1(sess, "io_read_buf");
! 428: goto out;
! 429: }
! 430:
! 431: /*
! 432: * If we're the last block, then we're assigned the
! 433: * remainder of the data.
! 434: */
! 435:
! 436: b->offs = offs;
! 437: b->idx = j;
! 438: b->len = (j == (s->blksz - 1) && s->rem) ?
! 439: s->rem : s->len;
! 440: offs += b->len;
! 441:
! 442: LOG4(sess, "%s: read block %zu, "
! 443: "length %zu B", path, b->idx, b->len);
! 444: }
! 445:
! 446: s->size = offs;
! 447: LOG3(sess, "%s: read blocks: %zu blocks, %jd B total "
! 448: "blocked data", path, s->blksz, (intmax_t)s->size);
! 449: return s;
! 450: out:
! 451: blkset_free(s);
! 452: return NULL;
! 453: }
! 454:
! 455: /*
! 456: * Symmetrise blk_recv_ack(), except w/o the leading identifier.
! 457: * Return zero on failure, non-zero on success.
! 458: */
! 459: int
! 460: blk_send_ack(struct sess *sess, int fd, struct blkset *p)
! 461: {
! 462: char buf[16];
! 463: size_t pos = 0, sz;
! 464:
! 465: /* Put the entire send routine into a buffer. */
! 466:
! 467: sz = sizeof(int32_t) + /* block count */
! 468: sizeof(int32_t) + /* block length */
! 469: sizeof(int32_t) + /* checksum length */
! 470: sizeof(int32_t); /* block remainder */
! 471: assert(sz <= sizeof(buf));
! 472:
! 473: if ( ! io_read_buf(sess, fd, buf, sz)) {
! 474: ERRX1(sess, "io_read_buf");
! 475: return 0;
! 476: }
! 477:
! 478: if ( ! io_unbuffer_size(sess, buf, &pos, sz, &p->blksz))
! 479: ERRX1(sess, "io_unbuffer_size");
! 480: else if ( ! io_unbuffer_size(sess, buf, &pos, sz, &p->len))
! 481: ERRX1(sess, "io_unbuffer_size");
! 482: else if ( ! io_unbuffer_size(sess, buf, &pos, sz, &p->csum))
! 483: ERRX1(sess, "io_unbuffer_size");
! 484: else if ( ! io_unbuffer_size(sess, buf, &pos, sz, &p->rem))
! 485: ERRX1(sess, "io_unbuffer_size");
! 486: else if (p->len && p->rem >= p->len)
! 487: ERRX1(sess, "non-zero length is less than remainder");
! 488: else if (0 == p->csum || p->csum > 16)
! 489: ERRX1(sess, "inappropriate checksum length");
! 490: else
! 491: return 1;
! 492:
! 493: return 0;
! 494: }
! 495:
! 496: /*
! 497: * The receiver now reads raw data and block indices from the sender,
! 498: * and merges them into the temporary file.
! 499: * Returns zero on failure, non-zero on success.
! 500: */
! 501: int
! 502: blk_merge(struct sess *sess, int fd, int ffd,
! 503: const struct blkset *block, int outfd, const char *path,
! 504: const void *map, size_t mapsz, float *stats)
! 505: {
! 506: size_t sz, tok;
! 507: int32_t rawtok;
! 508: char *buf = NULL;
! 509: void *pp;
! 510: ssize_t ssz;
! 511: int rc = 0;
! 512: unsigned char md[MD4_DIGEST_LENGTH],
! 513: ourmd[MD4_DIGEST_LENGTH];
! 514: off_t total = 0, fromcopy = 0, fromdown = 0;
! 515: MD4_CTX ctx;
! 516:
! 517: MD4_Init(&ctx);
! 518:
! 519: rawtok = htole32(sess->seed);
! 520: MD4_Update(&ctx, (unsigned char *)&rawtok, sizeof(int32_t));
! 521:
! 522: for (;;) {
! 523: /*
! 524: * This matches the sequence in blk_flush().
! 525: * We read the size/token, then optionally the data.
! 526: * The size >0 for reading data, 0 for no more data, and
! 527: * <0 for a token indicator.
! 528: */
! 529:
! 530: if ( ! io_read_int(sess, fd, &rawtok)) {
! 531: ERRX1(sess, "io_read_int");
! 532: goto out;
! 533: } else if (0 == rawtok)
! 534: break;
! 535:
! 536: if (rawtok > 0) {
! 537: sz = rawtok;
! 538: if (NULL == (pp = realloc(buf, sz))) {
! 539: ERR(sess, "realloc");
! 540: goto out;
! 541: }
! 542: buf = pp;
! 543: if ( ! io_read_buf(sess, fd, buf, sz)) {
! 544: ERRX1(sess, "io_read_int");
! 545: goto out;
! 546: }
! 547:
! 548: if ((ssz = write(outfd, buf, sz)) < 0) {
! 549: ERR(sess, "write: temporary file");
! 550: goto out;
! 551: } else if ((size_t)ssz != sz) {
! 552: ERRX(sess, "write: short write");
! 553: goto out;
! 554: }
! 555:
! 556: fromdown += sz;
! 557: total += sz;
! 558: LOG4(sess, "%s: received %zd B block, now %jd "
! 559: "B total", path, ssz, (intmax_t)total);
! 560:
! 561: MD4_Update(&ctx, buf, sz);
! 562: } else {
! 563: tok = -rawtok - 1;
! 564: if (tok >= block->blksz) {
! 565: ERRX(sess, "token not in block set");
! 566: goto out;
! 567: }
! 568:
! 569: /*
! 570: * Now we read from our block.
! 571: * We should only be at this point if we have a
! 572: * block to read from, i.e., if we were able to
! 573: * map our origin file and create a block
! 574: * profile from it.
! 575: */
! 576:
! 577: assert(MAP_FAILED != map);
! 578:
! 579: ssz = write(outfd,
! 580: map + block->blks[tok].offs,
! 581: block->blks[tok].len);
! 582:
! 583: if (ssz < 0) {
! 584: ERR(sess, "write: temporary file");
! 585: goto out;
! 586: } else if ((size_t)ssz != block->blks[tok].len) {
! 587: ERRX(sess, "write: short write");
! 588: goto out;
! 589: }
! 590:
! 591: fromcopy += block->blks[tok].len;
! 592: total += block->blks[tok].len;
! 593: LOG4(sess, "%s: copied %zu B, now %jd "
! 594: "B total", path, block->blks[tok].len,
! 595: (intmax_t)total);
! 596:
! 597: MD4_Update(&ctx,
! 598: map + block->blks[tok].offs,
! 599: block->blks[tok].len);
! 600: }
! 601: }
! 602:
! 603:
! 604: /* Make sure our resulting MD4_ hashes match. */
! 605:
! 606: MD4_Final(ourmd, &ctx);
! 607:
! 608: if ( ! io_read_buf(sess, fd, md, MD4_DIGEST_LENGTH)) {
! 609: ERRX1(sess, "io_read_buf");
! 610: goto out;
! 611: } else if (memcmp(md, ourmd, MD4_DIGEST_LENGTH)) {
! 612: ERRX(sess, "%s: file hash does not match", path);
! 613: goto out;
! 614: }
! 615:
! 616: *stats = 100.0 * fromdown / total;
! 617: rc = 1;
! 618: out:
! 619: free(buf);
! 620: return rc;
! 621: }
! 622:
! 623: /*
! 624: * Transmit the metadata for set and blocks.
! 625: * Return zero on failure, non-zero on success.
! 626: */
! 627: int
! 628: blk_send(struct sess *sess, int fd, size_t idx,
! 629: const struct blkset *p, const char *path)
! 630: {
! 631: char *buf;
! 632: size_t i, pos = 0, sz;
! 633: int rc = 0;
! 634:
! 635: /* Put the entire send routine into a buffer. */
! 636:
! 637: sz = sizeof(int32_t) + /* identifier */
! 638: sizeof(int32_t) + /* block count */
! 639: sizeof(int32_t) + /* block length */
! 640: sizeof(int32_t) + /* checksum length */
! 641: sizeof(int32_t) + /* block remainder */
! 642: p->blksz *
! 643: (sizeof(int32_t) + /* short checksum */
! 644: p->csum); /* long checksum */
! 645:
! 646: if (NULL == (buf = malloc(sz))) {
! 647: ERR(sess, "malloc");
! 648: return 0;
! 649: }
! 650:
! 651: io_buffer_int(sess, buf, &pos, sz, idx);
! 652: io_buffer_int(sess, buf, &pos, sz, p->blksz);
! 653: io_buffer_int(sess, buf, &pos, sz, p->len);
! 654: io_buffer_int(sess, buf, &pos, sz, p->csum);
! 655: io_buffer_int(sess, buf, &pos, sz, p->rem);
! 656:
! 657: for (i = 0; i < p->blksz; i++) {
! 658: io_buffer_int(sess, buf, &pos,
! 659: sz, p->blks[i].chksum_short);
! 660: io_buffer_buf(sess, buf, &pos, sz,
! 661: p->blks[i].chksum_long, p->csum);
! 662: }
! 663:
! 664: assert(pos == sz);
! 665:
! 666: if ( ! io_write_buf(sess, fd, buf, sz)) {
! 667: ERRX1(sess, "io_write_buf");
! 668: goto out;
! 669: }
! 670:
! 671: LOG3(sess, "%s: sent block prologue: %zu blocks of %zu B, "
! 672: "%zu B remainder, %zu B checksum", path,
! 673: p->blksz, p->len, p->rem, p->csum);
! 674: rc = 1;
! 675: out:
! 676: free(buf);
! 677: return rc;
! 678: }