Annotation of src/usr.bin/rsync/blocks.c, Revision 1.2
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: {
1.2 ! benno 164: off_t offs, last, end, fromcopy = 0, fromdown = 0,
1.1 benno 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: {
1.2 ! benno 241: int nfd, rc = 0, c;
1.1 benno 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:
1.2 ! benno 296: /*
1.1 benno 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:
1.2 ! benno 411: /*
1.1 benno 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 */
1.2 ! benno 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 */
1.1 benno 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++) {
1.2 ! benno 658: io_buffer_int(sess, buf, &pos,
1.1 benno 659: sz, p->blks[i].chksum_short);
1.2 ! benno 660: io_buffer_buf(sess, buf, &pos, sz,
1.1 benno 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: }