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