version 1.8, 2019/02/16 16:57:48 |
version 1.9, 2019/02/16 16:58:39 |
|
|
#include "extern.h" |
#include "extern.h" |
|
|
/* |
/* |
* Flush out "size" bytes of the buffer, doing all of the appropriate |
|
* chunking of the data, then the subsequent token (or zero). |
|
* Return zero on failure, non-zero on success. |
|
*/ |
|
static int |
|
blk_flush(struct sess *sess, int fd, |
|
const void *b, off_t size, int32_t token) |
|
{ |
|
off_t i = 0, sz; |
|
|
|
while (i < size) { |
|
sz = MAX_CHUNK < (size - i) ? |
|
MAX_CHUNK : (size - i); |
|
if (!io_write_int(sess, fd, sz)) { |
|
ERRX1(sess, "io_write_int"); |
|
return 0; |
|
} else if (!io_write_buf(sess, fd, b + i, sz)) { |
|
ERRX1(sess, "io_write_buf"); |
|
return 0; |
|
} |
|
i += sz; |
|
} |
|
|
|
if (!io_write_int(sess, fd, token)) { |
|
ERRX1(sess, "io_write_int"); |
|
return 0; |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
/* |
|
* From our current position of "offs" in buffer "buf" of total size |
* From our current position of "offs" in buffer "buf" of total size |
* "size", see if we can find a matching block in our list of blocks. |
* "size", see if we can find a matching block in our list of blocks. |
* The "hint" refers to the block that *might* work. |
* The "hint" refers to the block that *might* work. |
|
|
} |
} |
|
|
/* |
/* |
* The main reconstruction algorithm on the sender side. |
* Given a local file "path" and the blocks created by a remote machine, |
* This is reentrant: it's meant to be called whenever "fd" unblocks for |
* find out which blocks of our file they don't have and send them. |
* writing by the sender. |
* This function is reentrant: it must be called while there's still |
* Scans byte-wise over the input file, looking for matching blocks in |
* data to send. |
* what the server sent us. |
|
* If a block is found, emit all data up until the block, then the token |
|
* for the block. |
|
* The receiving end can then reconstruct the file trivially. |
|
* Return zero on failure, non-zero on success. |
|
*/ |
*/ |
static int |
void |
blk_match_send(struct sess *sess, const char *path, int fd, |
blk_match(struct sess *sess, const struct blkset *blks, |
const struct blkset *blks, struct blkstat *st) |
const char *path, struct blkstat *st) |
{ |
{ |
off_t last, end, sz; |
off_t last, end, sz; |
int32_t tok; |
int32_t tok; |
struct blk *blk; |
struct blk *blk; |
|
|
/* |
/* |
* Stop searching at the length of the file minus the size of |
* If the file's empty or we don't have any blocks from the |
* the last block. |
* sender, then simply send the whole file. |
* The reason for this being that we don't need to do an |
* Otherwise, run the hash matching routine and send raw chunks |
* incremental hash within the last block---if it doesn't match, |
* and subsequent matching tokens. |
* it doesn't match. |
|
*/ |
*/ |
|
|
end = st->mapsz + 1 - blks->blks[blks->blksz - 1].len; |
if (st->mapsz && blks->blksz) { |
last = st->offs; |
|
|
|
for ( ; st->offs < end; st->offs++) { |
|
blk = blk_find(sess, st->map, st->mapsz, |
|
st->offs, blks, path, st->hint); |
|
if (blk == NULL) |
|
continue; |
|
|
|
sz = st->offs - last; |
|
st->dirty += sz; |
|
st->total += sz; |
|
LOG4(sess, "%s: flushing %jd B before %zu B " |
|
"block %zu", path, (intmax_t)sz, blk->len, blk->idx); |
|
tok = -(blk->idx + 1); |
|
|
|
/* |
/* |
* Write the data we have, then follow it with the tag |
* Stop searching at the length of the file minus the |
* of the block that matches. |
* size of the last block. |
* The receiver will then write our data, then the data |
* The reason for this being that we don't need to do an |
* it already has in the matching block. |
* incremental hash within the last block---if it |
|
* doesn't match, it doesn't match. |
*/ |
*/ |
|
|
if (!blk_flush(sess, fd, st->map + last, sz, tok)) { |
end = st->mapsz + 1 - blks->blks[blks->blksz - 1].len; |
ERRX1(sess, "blk_flush"); |
last = st->offs; |
return -1; |
|
} |
|
|
|
st->total += blk->len; |
for ( ; st->offs < end; st->offs++) { |
st->offs += blk->len; |
blk = blk_find(sess, st->map, st->mapsz, |
st->hint = blk->idx + 1; |
st->offs, blks, path, st->hint); |
return 0; |
if (blk == NULL) |
} |
continue; |
|
|
/* Emit remaining data and send terminator token. */ |
sz = st->offs - last; |
|
st->dirty += sz; |
|
st->total += sz; |
|
LOG4(sess, "%s: flushing %jd B before %zu B " |
|
"block %zu", path, (intmax_t)sz, |
|
blk->len, blk->idx); |
|
tok = -(blk->idx + 1); |
|
|
sz = st->mapsz - last; |
/* |
st->total += sz; |
* Write the data we have, then follow it with |
st->dirty += sz; |
* the tag of the block that matches. |
|
*/ |
|
|
LOG4(sess, "%s: flushing remaining %jd B", path, (intmax_t)sz); |
st->curpos = last; |
|
st->curlen = st->curpos + sz; |
|
st->curtok = tok; |
|
assert(0 != st->curtok); |
|
st->curst = sz ? BLKSTAT_DATA : BLKSTAT_TOK; |
|
st->total += blk->len; |
|
st->offs += blk->len; |
|
st->hint = blk->idx + 1; |
|
return; |
|
} |
|
|
if (!blk_flush(sess, fd, st->map + last, sz, 0)) { |
/* Emit remaining data and send terminator token. */ |
ERRX1(sess, "blk_flush"); |
|
return -1; |
|
} |
|
|
|
LOG3(sess, "%s: flushed (chunked) %jd B total, " |
sz = st->mapsz - last; |
"%.2f%% upload ratio", path, (intmax_t)st->total, |
LOG4(sess, "%s: flushing remaining %jd B", |
100.0 * st->dirty / st->total); |
path, (intmax_t)sz); |
return 1; |
|
} |
|
|
|
/* |
st->total += sz; |
* Given a local file "path" and the blocks created by a remote machine, |
st->dirty += sz; |
* find out which blocks of our file they don't have and send them. |
st->curpos = last; |
* This function is reentrant: it must be called while there's still |
st->curlen = st->curpos + sz; |
* data to send. |
st->curtok = 0; |
* Return 0 if there's more data to send, >0 if the file has completed |
st->curst = sz ? BLKSTAT_DATA : BLKSTAT_TOK; |
* its update, or <0 on error. |
|
*/ |
|
int |
|
blk_match(struct sess *sess, int fd, const struct blkset *blks, |
|
const char *path, struct blkstat *st) |
|
{ |
|
unsigned char filemd[MD4_DIGEST_LENGTH]; |
|
int c; |
|
|
|
/* |
|
* If the file's empty or we don't have any blocks from the |
|
* sender, then simply send the whole file. |
|
* Otherwise, run the hash matching routine and send raw chunks |
|
* and subsequent matching tokens. |
|
*/ |
|
|
|
if (st->mapsz && blks->blksz) { |
|
if ((c = blk_match_send(sess, path, fd, blks, st)) < 0) { |
|
ERRX1(sess, "blk_match_send"); |
|
return -1; |
|
} else if (c == 0) |
|
return 0; |
|
} else { |
} else { |
if (!blk_flush(sess, fd, st->map, st->mapsz, 0)) { |
st->curpos = 0; |
ERRX1(sess, "blk_flush"); |
st->curlen = st->mapsz; |
return -1; |
st->curtok = 0; |
} |
st->curst = st->mapsz ? BLKSTAT_DATA : BLKSTAT_TOK; |
LOG3(sess, "%s: flushed (un-chunked) %jd B, 100%% upload ratio", |
st->dirty = st->total = st->mapsz; |
path, (intmax_t)st->mapsz); |
|
} |
|
|
|
/* |
LOG4(sess, "%s: flushing whole file %zu B", |
* Now write the full file hash. |
path, st->mapsz); |
* Since we're seeding the hash, this always gives us some sort |
|
* of data even if the file's zero-length. |
|
*/ |
|
|
|
hash_file(st->map, st->mapsz, filemd, sess); |
|
|
|
if (!io_write_buf(sess, fd, filemd, MD4_DIGEST_LENGTH)) { |
|
ERRX1(sess, "io_write_buf"); |
|
return -1; |
|
} |
} |
|
|
return 1; |
|
} |
} |
|
|
/* |
/* |