2012-06-21 14:57:05 +00:00
|
|
|
/*
|
|
|
|
* rabin_polynomial.c
|
|
|
|
*
|
2012-07-19 16:11:07 +00:00
|
|
|
* The rabin polynomial computation is derived from:
|
|
|
|
* http://code.google.com/p/rabin-fingerprint-c/
|
2012-06-21 14:57:05 +00:00
|
|
|
*
|
2012-07-19 16:11:07 +00:00
|
|
|
* originally created by Joel Lawrence Tucci on 09-March-2011.
|
|
|
|
*
|
|
|
|
* Rabin polynomial portions Copyright (c) 2011 Joel Lawrence Tucci
|
2012-06-21 14:57:05 +00:00
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions
|
|
|
|
* are met:
|
|
|
|
*
|
|
|
|
* Redistributions of source code must retain the above copyright notice,
|
|
|
|
* this list of conditions and the following disclaimer.
|
|
|
|
*
|
|
|
|
* Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
|
|
|
*
|
|
|
|
* Neither the name of the project's author nor the names of its
|
|
|
|
* contributors may be used to endorse or promote products derived from
|
|
|
|
* this software without specific prior written permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
|
|
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
|
|
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
|
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2012-06-21 15:10:43 +00:00
|
|
|
/*
|
|
|
|
* This file is a part of Pcompress, a chunked parallel multi-
|
|
|
|
* algorithm lossless compression and decompression program.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2012 Moinak Ghosh. All rights reserved.
|
|
|
|
* Use is subject to license terms.
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
2012-07-07 16:48:29 +00:00
|
|
|
* version 3 of the License, or (at your option) any later version.
|
2012-06-21 15:10:43 +00:00
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* moinakg@belenix.org, http://moinakg.wordpress.com/
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2012-06-21 14:57:05 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <allocator.h>
|
|
|
|
#include <utils.h>
|
|
|
|
|
|
|
|
#include "rabin_polynomial.h"
|
|
|
|
|
2012-07-01 16:14:02 +00:00
|
|
|
extern int lzma_init(void **data, int *level, ssize_t chunksize);
|
|
|
|
extern int lzma_compress(void *src, size_t srclen, void *dst,
|
|
|
|
size_t *destlen, int level, uchar_t chdr, void *data);
|
|
|
|
extern int lzma_decompress(void *src, size_t srclen, void *dst,
|
|
|
|
size_t *dstlen, int level, uchar_t chdr, void *data);
|
|
|
|
extern int lzma_deinit(void **data);
|
2012-07-19 16:11:07 +00:00
|
|
|
extern int bsdiff(u_char *old, bsize_t oldsize, u_char *new, bsize_t newsize,
|
|
|
|
u_char *diff, u_char *scratch, bsize_t scratchsize);
|
|
|
|
extern bsize_t get_bsdiff_sz(u_char *pbuf);
|
|
|
|
extern int bspatch(u_char *pbuf, u_char *old, bsize_t oldsize, u_char *new,
|
|
|
|
bsize_t *_newsize);
|
2012-07-01 16:14:02 +00:00
|
|
|
|
2012-07-03 17:17:24 +00:00
|
|
|
uint32_t rabin_polynomial_max_block_size = RAB_POLYNOMIAL_MAX_BLOCK_SIZE;
|
2012-06-21 14:57:05 +00:00
|
|
|
|
|
|
|
/*
|
2012-06-29 12:53:55 +00:00
|
|
|
* Initialize the algorithm with the default params.
|
2012-06-21 14:57:05 +00:00
|
|
|
*/
|
|
|
|
rabin_context_t *
|
2012-07-19 16:11:07 +00:00
|
|
|
create_rabin_context(uint64_t chunksize, uint64_t real_chunksize, const char *algo, int delta_flag) {
|
2012-06-21 14:57:05 +00:00
|
|
|
rabin_context_t *ctx;
|
|
|
|
unsigned char *current_window_data;
|
2012-07-09 17:58:11 +00:00
|
|
|
uint32_t blknum;
|
2012-06-29 12:53:55 +00:00
|
|
|
|
2012-07-10 14:44:23 +00:00
|
|
|
/*
|
|
|
|
* Rabin window size must be power of 2 for optimization.
|
|
|
|
*/
|
|
|
|
if (!ISP2(RAB_POLYNOMIAL_WIN_SIZE)) {
|
|
|
|
fprintf(stderr, "Rabin window size must be a power of 2 in range 4 <= x <= 64\n");
|
|
|
|
return (NULL);
|
|
|
|
}
|
2012-07-19 16:11:07 +00:00
|
|
|
|
|
|
|
if (chunksize < RAB_MIN_CHUNK_SIZE) {
|
|
|
|
fprintf(stderr, "Minimum chunk size for Dedup must be %l bytes\n",
|
|
|
|
RAB_MIN_CHUNK_SIZE);
|
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
|
2012-07-03 17:17:24 +00:00
|
|
|
/*
|
2012-07-19 16:11:07 +00:00
|
|
|
* For LZMA with chunksize <= LZMA Window size and/or Delta enabled we
|
|
|
|
* use 4K minimum Rabin block size. For everything else it is 2K based
|
|
|
|
* on experimentation.
|
2012-07-03 17:17:24 +00:00
|
|
|
*/
|
|
|
|
ctx = (rabin_context_t *)slab_alloc(NULL, sizeof (rabin_context_t));
|
|
|
|
ctx->rabin_poly_max_block_size = RAB_POLYNOMIAL_MAX_BLOCK_SIZE;
|
2012-07-19 16:11:07 +00:00
|
|
|
if (((memcmp(algo, "lzma", 4) == 0 || memcmp(algo, "adapt", 5) == 0) &&
|
|
|
|
chunksize <= LZMA_WINDOW_MAX) || delta_flag) {
|
2012-07-03 17:17:24 +00:00
|
|
|
ctx->rabin_poly_min_block_size = RAB_POLYNOMIAL_MIN_BLOCK_SIZE;
|
|
|
|
ctx->rabin_avg_block_mask = RAB_POLYNOMIAL_AVG_BLOCK_MASK;
|
|
|
|
ctx->rabin_poly_avg_block_size = RAB_POLYNOMIAL_AVG_BLOCK_SIZE;
|
2012-07-06 17:54:12 +00:00
|
|
|
ctx->rabin_break_patt = RAB_POLYNOMIAL_CONST;
|
2012-07-03 17:17:24 +00:00
|
|
|
} else {
|
|
|
|
ctx->rabin_poly_min_block_size = RAB_POLYNOMIAL_MIN_BLOCK_SIZE2;
|
|
|
|
ctx->rabin_avg_block_mask = RAB_POLYNOMIAL_AVG_BLOCK_MASK2;
|
|
|
|
ctx->rabin_poly_avg_block_size = RAB_POLYNOMIAL_AVG_BLOCK_SIZE2;
|
2012-07-06 17:54:12 +00:00
|
|
|
ctx->rabin_break_patt = 0;
|
2012-07-03 17:17:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
blknum = chunksize / ctx->rabin_poly_min_block_size;
|
|
|
|
if (chunksize % ctx->rabin_poly_min_block_size)
|
2012-06-29 12:53:55 +00:00
|
|
|
blknum++;
|
2012-06-21 14:57:05 +00:00
|
|
|
|
2012-07-02 16:38:03 +00:00
|
|
|
if (blknum > RABIN_MAX_BLOCKS) {
|
|
|
|
fprintf(stderr, "Chunk size too large for dedup.\n");
|
|
|
|
destroy_rabin_context(ctx);
|
|
|
|
return (NULL);
|
|
|
|
}
|
2012-06-21 14:57:05 +00:00
|
|
|
current_window_data = slab_alloc(NULL, RAB_POLYNOMIAL_WIN_SIZE);
|
2012-07-08 16:14:08 +00:00
|
|
|
ctx->blocks = NULL;
|
|
|
|
if (real_chunksize > 0) {
|
|
|
|
ctx->blocks = (rabin_blockentry_t *)slab_alloc(NULL,
|
|
|
|
blknum * ctx->rabin_poly_min_block_size);
|
|
|
|
}
|
|
|
|
if(ctx == NULL || current_window_data == NULL || (ctx->blocks == NULL && real_chunksize > 0)) {
|
2012-06-21 14:57:05 +00:00
|
|
|
fprintf(stderr,
|
|
|
|
"Could not allocate rabin polynomial context, out of memory\n");
|
2012-07-01 16:14:02 +00:00
|
|
|
destroy_rabin_context(ctx);
|
2012-06-21 14:57:05 +00:00
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
|
2012-07-08 16:14:08 +00:00
|
|
|
ctx->lzma_data = NULL;
|
2012-07-19 16:11:07 +00:00
|
|
|
ctx->level = 14;
|
2012-07-08 16:14:08 +00:00
|
|
|
if (real_chunksize > 0) {
|
|
|
|
lzma_init(&(ctx->lzma_data), &(ctx->level), chunksize);
|
|
|
|
if (!(ctx->lzma_data)) {
|
|
|
|
fprintf(stderr,
|
2012-07-19 16:11:07 +00:00
|
|
|
"Could not initialize LZMA data for rabin index, out of memory\n");
|
2012-07-08 16:14:08 +00:00
|
|
|
destroy_rabin_context(ctx);
|
|
|
|
return (NULL);
|
|
|
|
}
|
2012-07-01 16:14:02 +00:00
|
|
|
}
|
2012-06-21 14:57:05 +00:00
|
|
|
/*
|
|
|
|
* We should compute the power for the window size.
|
|
|
|
* static uint64_t polynomial_pow;
|
|
|
|
* polynomial_pow = 1;
|
|
|
|
* for(index=0; index<RAB_POLYNOMIAL_WIN_SIZE; index++) {
|
|
|
|
* polynomial_pow *= RAB_POLYNOMIAL_CONST;
|
|
|
|
* }
|
|
|
|
* But since RAB_POLYNOMIAL_CONST == 2, any expression of the form
|
|
|
|
* x * polynomial_pow can we written as x << RAB_POLYNOMIAL_WIN_SIZE
|
|
|
|
*/
|
|
|
|
|
2012-06-29 12:53:55 +00:00
|
|
|
ctx->current_window_data = current_window_data;
|
2012-07-05 18:46:02 +00:00
|
|
|
ctx->real_chunksize = real_chunksize;
|
2012-07-19 16:11:07 +00:00
|
|
|
ctx->delta_flag = delta_flag;
|
2012-06-29 12:53:55 +00:00
|
|
|
reset_rabin_context(ctx);
|
|
|
|
return (ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
reset_rabin_context(rabin_context_t *ctx)
|
|
|
|
{
|
|
|
|
memset(ctx->current_window_data, 0, RAB_POLYNOMIAL_WIN_SIZE);
|
2012-06-21 14:57:05 +00:00
|
|
|
ctx->window_pos = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
destroy_rabin_context(rabin_context_t *ctx)
|
|
|
|
{
|
2012-07-01 16:14:02 +00:00
|
|
|
if (ctx) {
|
|
|
|
if (ctx->current_window_data) slab_free(NULL, ctx->current_window_data);
|
|
|
|
if (ctx->blocks) slab_free(NULL, ctx->blocks);
|
|
|
|
if (ctx->lzma_data) lzma_deinit(&(ctx->lzma_data));
|
|
|
|
slab_free(NULL, ctx);
|
|
|
|
}
|
2012-06-21 14:57:05 +00:00
|
|
|
}
|
|
|
|
|
2012-06-29 12:53:55 +00:00
|
|
|
/*
|
|
|
|
* Checksum Comparator for qsort
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
cmpblks(const void *a, const void *b)
|
|
|
|
{
|
|
|
|
rabin_blockentry_t *a1 = (rabin_blockentry_t *)a;
|
|
|
|
rabin_blockentry_t *b1 = (rabin_blockentry_t *)b;
|
|
|
|
|
2012-07-19 16:11:07 +00:00
|
|
|
if (a1->cksum_n_offset < b1->cksum_n_offset) {
|
2012-06-29 12:53:55 +00:00
|
|
|
return (-1);
|
2012-07-19 16:11:07 +00:00
|
|
|
} else if (a1->cksum_n_offset == b1->cksum_n_offset) {
|
|
|
|
/*
|
|
|
|
* If fingerprints match then compare lengths. Length match makes
|
|
|
|
* for strong exact detection/ordering during sort while stopping
|
|
|
|
* short of expensive memcmp().
|
|
|
|
*/
|
|
|
|
if (a1->length < b1->length) {
|
|
|
|
return (-1);
|
|
|
|
} else if (a1->length == b1->length) {
|
|
|
|
return (0);
|
|
|
|
} else if (a1->length > b1->length) {
|
|
|
|
return (1);
|
|
|
|
}
|
|
|
|
} else if (a1->cksum_n_offset > b1->cksum_n_offset) {
|
2012-06-29 12:53:55 +00:00
|
|
|
return (1);
|
2012-07-19 16:11:07 +00:00
|
|
|
}
|
2012-06-29 12:53:55 +00:00
|
|
|
}
|
|
|
|
|
2012-06-21 14:57:05 +00:00
|
|
|
/**
|
2012-07-04 18:09:03 +00:00
|
|
|
* Perform Deduplication based on Rabin Fingerprinting. A 31-byte window is used for
|
2012-06-29 12:53:55 +00:00
|
|
|
* the rolling checksum and dedup blocks vary in size from 4K-128K.
|
2012-06-21 14:57:05 +00:00
|
|
|
*/
|
2012-07-03 17:17:24 +00:00
|
|
|
uint32_t
|
2012-07-08 16:14:08 +00:00
|
|
|
rabin_dedup(rabin_context_t *ctx, uchar_t *buf, ssize_t *size, ssize_t offset, ssize_t *rabin_pos)
|
2012-06-21 14:57:05 +00:00
|
|
|
{
|
2012-07-19 16:11:07 +00:00
|
|
|
ssize_t i, last_offset, j, fplist_sz;
|
2012-07-03 17:17:24 +00:00
|
|
|
uint32_t blknum;
|
2012-06-29 12:53:55 +00:00
|
|
|
char *buf1 = (char *)buf;
|
2012-07-03 17:17:24 +00:00
|
|
|
uint32_t length;
|
2012-07-19 16:11:07 +00:00
|
|
|
uint64_t cur_roll_checksum, cur_sketch;
|
|
|
|
uint64_t *fplist;
|
|
|
|
uint32_t len1, fpos;
|
2012-06-21 14:57:05 +00:00
|
|
|
|
2012-07-19 16:11:07 +00:00
|
|
|
if (rabin_pos == NULL) {
|
|
|
|
/*
|
|
|
|
* Initialize arrays for sketch computation. We re-use memory allocated
|
|
|
|
* for the compressed chunk temporarily.
|
|
|
|
*/
|
|
|
|
fplist_sz = 8 * ctx->rabin_poly_avg_block_size;
|
|
|
|
fplist = (uint64_t *)(ctx->cbuf + ctx->real_chunksize - fplist_sz);
|
|
|
|
memset(fplist, 0, fplist_sz);
|
|
|
|
fpos = 0;
|
|
|
|
len1 = 0;
|
|
|
|
}
|
2012-06-29 12:53:55 +00:00
|
|
|
length = offset;
|
2012-06-21 14:57:05 +00:00
|
|
|
last_offset = 0;
|
2012-06-29 12:53:55 +00:00
|
|
|
blknum = 0;
|
|
|
|
ctx->valid = 0;
|
2012-07-19 16:11:07 +00:00
|
|
|
cur_roll_checksum = 0;
|
2012-07-10 14:44:23 +00:00
|
|
|
j = 0;
|
2012-07-19 16:11:07 +00:00
|
|
|
cur_sketch = 0;
|
2012-06-21 14:57:05 +00:00
|
|
|
|
2012-07-08 16:14:08 +00:00
|
|
|
/*
|
|
|
|
* If rabin_pos is non-zero then we are being asked to scan for the last rabin boundary
|
|
|
|
* in the chunk. We start scanning at chunk end - max rabin block size. We avoid doing
|
|
|
|
* a full chunk scan.
|
|
|
|
*/
|
|
|
|
if (rabin_pos) {
|
|
|
|
offset = *size - RAB_POLYNOMIAL_MAX_BLOCK_SIZE;
|
|
|
|
}
|
2012-07-03 17:17:24 +00:00
|
|
|
if (*size < ctx->rabin_poly_avg_block_size) return;
|
2012-06-29 12:53:55 +00:00
|
|
|
for (i=offset; i<*size; i++) {
|
|
|
|
char cur_byte = buf1[i];
|
2012-06-21 14:57:05 +00:00
|
|
|
uint64_t pushed_out = ctx->current_window_data[ctx->window_pos];
|
|
|
|
ctx->current_window_data[ctx->window_pos] = cur_byte;
|
|
|
|
/*
|
|
|
|
* We want to do:
|
|
|
|
* cur_roll_checksum = cur_roll_checksum * RAB_POLYNOMIAL_CONST + cur_byte;
|
|
|
|
* cur_roll_checksum -= pushed_out * polynomial_pow;
|
|
|
|
*
|
|
|
|
* However since RAB_POLYNOMIAL_CONST == 2, we use shifts.
|
|
|
|
*/
|
2012-07-19 16:11:07 +00:00
|
|
|
cur_roll_checksum = (cur_roll_checksum << 1) + cur_byte;
|
|
|
|
cur_roll_checksum -= (pushed_out << RAB_POLYNOMIAL_WIN_SIZE);
|
2012-06-21 14:57:05 +00:00
|
|
|
|
2012-07-19 16:11:07 +00:00
|
|
|
/*
|
|
|
|
* Compute a super sketch value of the block. We store a sum of relative
|
|
|
|
* maximal rabin hash values per 1K(SKETCH_BASIC_BLOCK_SZ) of data. So we
|
|
|
|
* get upto 128 sums for a max block size of 128K. This is a representative
|
|
|
|
* fingerprint sketch of the block. Storing and comparing upto 128 fingerprints
|
|
|
|
* per block is very expensive (compute & RAM) so we eventually sum all the
|
|
|
|
* fingerprints for the block to create a single super sketch value representing
|
|
|
|
* maximal features of the block.
|
|
|
|
*
|
|
|
|
* This value can be used for similarity detection for delta encoding. Exact
|
|
|
|
* match for deduplication is additionally detected via a memcmp(). This is a
|
|
|
|
* variant of some approaches detailed in:
|
|
|
|
* http://www.armedia.com/wp/SimilarityIndex.pdf
|
|
|
|
*/
|
|
|
|
if (rabin_pos == NULL) {
|
|
|
|
len1++;
|
|
|
|
j = cur_roll_checksum & ctx->rabin_avg_block_mask;
|
|
|
|
fplist[j] += cur_roll_checksum;
|
|
|
|
if (fplist[j] > fplist[fpos]) fpos = j;
|
|
|
|
if (len1 == SKETCH_BASIC_BLOCK_SZ) {
|
|
|
|
/*
|
|
|
|
* Compute the super sketch value by summing all the representative
|
|
|
|
* fingerprints of the block.
|
|
|
|
*/
|
|
|
|
cur_sketch += fplist[fpos];
|
|
|
|
memset(fplist, 0, fplist_sz);
|
|
|
|
fpos = 0;
|
|
|
|
len1 = 0;
|
|
|
|
}
|
|
|
|
}
|
2012-07-13 16:36:55 +00:00
|
|
|
/*
|
|
|
|
* Window pos has to rotate from 0 .. RAB_POLYNOMIAL_WIN_SIZE-1
|
|
|
|
* We avoid a branch here by masking. This requires RAB_POLYNOMIAL_WIN_SIZE
|
|
|
|
* to be power of 2
|
|
|
|
*/
|
2012-07-10 14:44:23 +00:00
|
|
|
ctx->window_pos = (ctx->window_pos + 1) & (RAB_POLYNOMIAL_WIN_SIZE-1);
|
|
|
|
length++;
|
2012-06-29 12:53:55 +00:00
|
|
|
|
2012-07-03 17:17:24 +00:00
|
|
|
if (length < ctx->rabin_poly_min_block_size) continue;
|
2012-06-29 12:53:55 +00:00
|
|
|
|
2012-06-21 15:10:43 +00:00
|
|
|
// If we hit our special value or reached the max block size update block offset
|
2012-07-19 16:11:07 +00:00
|
|
|
if ((cur_roll_checksum & ctx->rabin_avg_block_mask) == ctx->rabin_break_patt ||
|
2012-06-21 14:57:05 +00:00
|
|
|
length >= rabin_polynomial_max_block_size) {
|
2012-07-08 16:14:08 +00:00
|
|
|
if (rabin_pos == NULL) {
|
|
|
|
ctx->blocks[blknum].offset = last_offset;
|
|
|
|
ctx->blocks[blknum].index = blknum; // Need to store for sorting
|
|
|
|
ctx->blocks[blknum].length = length;
|
|
|
|
ctx->blocks[blknum].refcount = 0;
|
2012-07-19 16:11:07 +00:00
|
|
|
ctx->blocks[blknum].similar = 0;
|
|
|
|
ctx->blocks[blknum].cksum_n_offset = cur_sketch;
|
|
|
|
memset(fplist, 0, fplist_sz);
|
|
|
|
fpos = 0;
|
|
|
|
len1 = 0;
|
|
|
|
cur_sketch = 0;
|
2012-07-08 16:14:08 +00:00
|
|
|
blknum++;
|
|
|
|
}
|
2012-06-21 14:57:05 +00:00
|
|
|
last_offset = i+1;
|
|
|
|
length = 0;
|
2012-07-13 16:36:55 +00:00
|
|
|
j = 0;
|
2012-06-21 14:57:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-07-08 16:14:08 +00:00
|
|
|
if (rabin_pos && last_offset < *size) {
|
|
|
|
*rabin_pos = last_offset;
|
|
|
|
return (0);
|
|
|
|
}
|
2012-07-10 15:41:31 +00:00
|
|
|
|
2012-06-29 12:53:55 +00:00
|
|
|
// If we found at least a few chunks, perform dedup.
|
|
|
|
if (blknum > 2) {
|
|
|
|
uint64_t prev_cksum;
|
2012-07-05 18:46:02 +00:00
|
|
|
uint32_t blk, prev_length;
|
|
|
|
ssize_t pos, matchlen, pos1;
|
2012-06-29 12:53:55 +00:00
|
|
|
int valid = 1;
|
|
|
|
char *tmp, *prev_offset;
|
2012-07-05 18:46:02 +00:00
|
|
|
uint32_t *blkarr, *trans, *rabin_index, prev_index, prev_blk;
|
|
|
|
ssize_t rabin_index_sz;
|
2012-06-29 12:53:55 +00:00
|
|
|
|
|
|
|
// Insert the last left-over trailing bytes, if any, into a block.
|
|
|
|
if (last_offset < *size) {
|
|
|
|
ctx->blocks[blknum].offset = last_offset;
|
|
|
|
ctx->blocks[blknum].index = blknum;
|
|
|
|
ctx->blocks[blknum].length = *size - last_offset;
|
2012-07-01 16:14:02 +00:00
|
|
|
ctx->blocks[blknum].refcount = 0;
|
2012-07-19 16:11:07 +00:00
|
|
|
ctx->blocks[blknum].similar = 0;
|
|
|
|
ctx->blocks[blknum].cksum_n_offset = cur_sketch;
|
2012-06-29 12:53:55 +00:00
|
|
|
blknum++;
|
|
|
|
last_offset = *size;
|
|
|
|
}
|
|
|
|
|
2012-07-01 16:14:02 +00:00
|
|
|
rabin_index_sz = (ssize_t)blknum * RABIN_ENTRY_SIZE;
|
2012-06-29 12:53:55 +00:00
|
|
|
prev_cksum = 0;
|
|
|
|
prev_length = 0;
|
|
|
|
prev_offset = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Now sort the block array based on checksums. This will bring virtually
|
|
|
|
* all similar block entries together. Effectiveness depends on how strong
|
2012-07-19 16:11:07 +00:00
|
|
|
* our checksum is. We are using a maximal super-sketch value.
|
2012-06-29 12:53:55 +00:00
|
|
|
*/
|
|
|
|
qsort(ctx->blocks, blknum, sizeof (rabin_blockentry_t), cmpblks);
|
2012-07-03 17:17:24 +00:00
|
|
|
rabin_index = (uint32_t *)(ctx->cbuf + RABIN_HDR_SIZE);
|
2012-07-05 18:46:02 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* We need 2 temporary arrays. We just use available space in the last
|
|
|
|
* portion of the buffer that will hold the deduped segment.
|
|
|
|
*/
|
|
|
|
blkarr = (uint32_t *)(ctx->cbuf + ctx->real_chunksize - (rabin_index_sz * 2 + 1));
|
|
|
|
trans = (uint32_t *)(ctx->cbuf + ctx->real_chunksize - (rabin_index_sz + 1));
|
2012-07-03 17:17:24 +00:00
|
|
|
matchlen = 0;
|
2012-06-29 12:53:55 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Now make a pass through the sorted block array making identical blocks
|
|
|
|
* point to the first identical block entry. A simple Run Length Encoding
|
|
|
|
* sort of. Checksums, length and contents (memcmp()) must match for blocks
|
|
|
|
* to be considered identical.
|
|
|
|
* The block index in the chunk is initialized with pointers into the
|
|
|
|
* sorted block array.
|
2012-06-29 18:15:06 +00:00
|
|
|
* A reference count is maintained for blocks that are similar with other
|
|
|
|
* blocks. This helps in non-duplicate block merging later.
|
2012-06-29 12:53:55 +00:00
|
|
|
*/
|
|
|
|
for (blk = 0; blk < blknum; blk++) {
|
2012-07-05 18:46:02 +00:00
|
|
|
blkarr[ctx->blocks[blk].index] = blk;
|
2012-06-29 12:53:55 +00:00
|
|
|
|
2012-07-05 18:46:02 +00:00
|
|
|
if (blk > 0 && ctx->blocks[blk].cksum_n_offset == prev_cksum &&
|
2012-06-29 12:53:55 +00:00
|
|
|
ctx->blocks[blk].length == prev_length &&
|
|
|
|
memcmp(prev_offset, buf1 + ctx->blocks[blk].offset, prev_length) == 0) {
|
2012-07-19 16:11:07 +00:00
|
|
|
ctx->blocks[blk].similar = SIMILAR_EXACT;
|
2012-06-29 18:15:06 +00:00
|
|
|
ctx->blocks[blk].index = prev_index;
|
|
|
|
(ctx->blocks[prev_blk].refcount)++;
|
2012-07-03 17:17:24 +00:00
|
|
|
matchlen += prev_length;
|
2012-06-29 12:53:55 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
prev_offset = buf1 + ctx->blocks[blk].offset;
|
2012-07-05 18:46:02 +00:00
|
|
|
prev_cksum = ctx->blocks[blk].cksum_n_offset;
|
2012-06-29 12:53:55 +00:00
|
|
|
prev_length = ctx->blocks[blk].length;
|
2012-06-29 18:15:06 +00:00
|
|
|
prev_index = ctx->blocks[blk].index;
|
|
|
|
prev_blk = blk;
|
2012-06-29 12:53:55 +00:00
|
|
|
}
|
2012-07-19 16:11:07 +00:00
|
|
|
|
|
|
|
if (ctx->delta_flag) {
|
|
|
|
for (blk = 0; blk < blknum; blk++) {
|
|
|
|
if (ctx->blocks[blk].similar) continue;
|
|
|
|
|
|
|
|
if (blk > 0 && ctx->blocks[blk].refcount == 0 &&
|
|
|
|
ctx->blocks[blk].cksum_n_offset == prev_cksum) {
|
|
|
|
ssize_t sz1, sz2;
|
|
|
|
ctx->blocks[blk].index = prev_index;
|
|
|
|
ctx->blocks[blk].similar = SIMILAR_PARTIAL;
|
|
|
|
(ctx->blocks[prev_blk].refcount)++;
|
|
|
|
matchlen += prev_length/2;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
prev_offset = buf1 + ctx->blocks[blk].offset;
|
|
|
|
prev_cksum = ctx->blocks[blk].cksum_n_offset;
|
|
|
|
prev_length = ctx->blocks[blk].length;
|
|
|
|
prev_index = ctx->blocks[blk].index;
|
|
|
|
prev_blk = blk;
|
|
|
|
}
|
|
|
|
}
|
2012-07-03 17:17:24 +00:00
|
|
|
if (matchlen < rabin_index_sz) {
|
2012-06-29 12:53:55 +00:00
|
|
|
ctx->valid = 0;
|
|
|
|
return;
|
|
|
|
}
|
2012-07-19 16:11:07 +00:00
|
|
|
|
2012-06-29 12:53:55 +00:00
|
|
|
/*
|
|
|
|
* Another pass, this time through the block index in the chunk. We insert
|
|
|
|
* block length into unique block entries. For block entries that are
|
2012-07-05 18:46:02 +00:00
|
|
|
* identical with another one we store the index number with msb set.
|
2012-06-29 12:53:55 +00:00
|
|
|
* This way we can differentiate between a unique block length entry and a
|
|
|
|
* pointer to another block without needing a separate flag.
|
|
|
|
*/
|
2012-06-29 18:15:06 +00:00
|
|
|
prev_index = 0;
|
|
|
|
prev_length = 0;
|
2012-07-05 18:46:02 +00:00
|
|
|
pos = 0;
|
2012-06-29 12:53:55 +00:00
|
|
|
for (blk = 0; blk < blknum; blk++) {
|
|
|
|
rabin_blockentry_t *be;
|
|
|
|
|
2012-07-05 18:46:02 +00:00
|
|
|
be = &(ctx->blocks[blkarr[blk]]);
|
2012-07-19 16:11:07 +00:00
|
|
|
if (be->similar == 0) {
|
2012-06-29 18:15:06 +00:00
|
|
|
/*
|
|
|
|
* Update Index entry with the length. Also try to merge runs
|
2012-07-19 16:11:07 +00:00
|
|
|
* of unique (non-duplicate/similar) blocks into a single block
|
|
|
|
* entry as long as the total length does not exceed max block
|
|
|
|
* size.
|
2012-06-29 18:15:06 +00:00
|
|
|
*/
|
|
|
|
if (prev_index == 0) {
|
|
|
|
if (be->refcount == 0) {
|
2012-07-05 18:46:02 +00:00
|
|
|
prev_index = pos;
|
2012-06-29 18:15:06 +00:00
|
|
|
prev_length = be->length;
|
|
|
|
}
|
2012-07-05 18:46:02 +00:00
|
|
|
rabin_index[pos] = be->length;
|
|
|
|
ctx->blocks[pos].cksum_n_offset = be->offset;
|
|
|
|
trans[blk] = pos;
|
|
|
|
pos++;
|
2012-06-29 18:15:06 +00:00
|
|
|
} else {
|
|
|
|
if (be->refcount > 0) {
|
|
|
|
prev_index = 0;
|
|
|
|
prev_length = 0;
|
2012-07-05 18:46:02 +00:00
|
|
|
rabin_index[pos] = be->length;
|
|
|
|
ctx->blocks[pos].cksum_n_offset = be->offset;
|
|
|
|
trans[blk] = pos;
|
|
|
|
pos++;
|
2012-06-29 18:15:06 +00:00
|
|
|
} else {
|
2012-07-02 16:38:03 +00:00
|
|
|
if (prev_length + be->length <= RABIN_MAX_BLOCK_SIZE) {
|
2012-06-29 18:15:06 +00:00
|
|
|
prev_length += be->length;
|
2012-07-05 18:46:02 +00:00
|
|
|
rabin_index[prev_index] = prev_length;
|
2012-06-29 18:15:06 +00:00
|
|
|
} else {
|
|
|
|
prev_index = 0;
|
|
|
|
prev_length = 0;
|
2012-07-05 18:46:02 +00:00
|
|
|
rabin_index[pos] = be->length;
|
|
|
|
ctx->blocks[pos].cksum_n_offset = be->offset;
|
|
|
|
trans[blk] = pos;
|
|
|
|
pos++;
|
2012-06-29 18:15:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-06-29 12:53:55 +00:00
|
|
|
} else {
|
2012-06-29 18:15:06 +00:00
|
|
|
prev_index = 0;
|
|
|
|
prev_length = 0;
|
2012-07-19 16:11:07 +00:00
|
|
|
ctx->blocks[pos].cksum_n_offset = be->offset;
|
|
|
|
ctx->blocks[pos].new_length = be->length;
|
2012-07-05 18:46:02 +00:00
|
|
|
trans[blk] = pos;
|
2012-07-19 16:11:07 +00:00
|
|
|
|
|
|
|
if (be->similar == SIMILAR_EXACT) {
|
|
|
|
rabin_index[pos] = (blkarr[be->index] | RABIN_INDEX_FLAG) &
|
|
|
|
CLEAR_SIMILARITY_FLAG;
|
|
|
|
} else {
|
|
|
|
rabin_index[pos] = blkarr[be->index] | RABIN_INDEX_FLAG |
|
|
|
|
SET_SIMILARITY_FLAG;
|
|
|
|
}
|
2012-07-05 18:46:02 +00:00
|
|
|
pos++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2012-07-19 16:11:07 +00:00
|
|
|
* Final pass, copy the data and perform delta encoding.
|
2012-07-05 18:46:02 +00:00
|
|
|
*/
|
|
|
|
blknum = pos;
|
|
|
|
rabin_index_sz = (ssize_t)pos * RABIN_ENTRY_SIZE;
|
|
|
|
pos1 = rabin_index_sz + RABIN_HDR_SIZE;
|
|
|
|
for (blk = 0; blk < blknum; blk++) {
|
2012-07-19 16:11:07 +00:00
|
|
|
uchar_t *old, *new;
|
|
|
|
int32_t bsz;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If blocks are overflowing the allowed chunk size then dedup did not
|
|
|
|
* help at all. We invalidate the dedup operation.
|
|
|
|
*/
|
|
|
|
if (pos1 > last_offset) {
|
|
|
|
valid = 0;
|
|
|
|
break;
|
|
|
|
}
|
2012-07-05 18:46:02 +00:00
|
|
|
if (rabin_index[blk] & RABIN_INDEX_FLAG) {
|
|
|
|
j = rabin_index[blk] & RABIN_INDEX_VALUE;
|
2012-07-19 16:11:07 +00:00
|
|
|
i = ctx->blocks[j].index;
|
|
|
|
|
|
|
|
if (rabin_index[blk] & GET_SIMILARITY_FLAG) {
|
|
|
|
old = buf1 + ctx->blocks[j].offset;
|
|
|
|
new = buf1 + ctx->blocks[blk].cksum_n_offset;
|
|
|
|
bsz = bsdiff(old, ctx->blocks[j].length, new,
|
|
|
|
ctx->blocks[blk].new_length, ctx->cbuf + pos1, 0, 0);
|
|
|
|
if (bsz == 0) {
|
|
|
|
memcpy(ctx->cbuf + pos1, new, ctx->blocks[blk].new_length);
|
|
|
|
rabin_index[blk] = htonl(ctx->blocks[blk].new_length);
|
|
|
|
pos1 += ctx->blocks[blk].new_length;
|
|
|
|
} else {
|
|
|
|
rabin_index[blk] = htonl(trans[i] |
|
|
|
|
RABIN_INDEX_FLAG | SET_SIMILARITY_FLAG);
|
|
|
|
pos1 += bsz;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
rabin_index[blk] = htonl(trans[i] | RABIN_INDEX_FLAG);
|
2012-07-05 18:46:02 +00:00
|
|
|
}
|
2012-07-19 16:11:07 +00:00
|
|
|
} else {
|
|
|
|
memcpy(ctx->cbuf + pos1, buf1 + ctx->blocks[blk].cksum_n_offset,
|
|
|
|
rabin_index[blk]);
|
2012-07-05 18:46:02 +00:00
|
|
|
pos1 += rabin_index[blk];
|
|
|
|
rabin_index[blk] = htonl(rabin_index[blk]);
|
2012-06-29 12:53:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
cont:
|
|
|
|
if (valid) {
|
2012-07-01 16:14:02 +00:00
|
|
|
uchar_t *cbuf = ctx->cbuf;
|
|
|
|
ssize_t *entries;
|
|
|
|
|
2012-07-03 17:17:24 +00:00
|
|
|
*((uint32_t *)cbuf) = htonl(blknum);
|
|
|
|
cbuf += sizeof (uint32_t);
|
2012-07-01 16:14:02 +00:00
|
|
|
entries = (ssize_t *)cbuf;
|
|
|
|
entries[0] = htonll(*size);
|
|
|
|
entries[1] = 0;
|
2012-07-05 18:46:02 +00:00
|
|
|
entries[2] = htonll(pos1 - rabin_index_sz - RABIN_HDR_SIZE);
|
|
|
|
*size = pos1;
|
2012-06-29 12:53:55 +00:00
|
|
|
ctx->valid = 1;
|
2012-07-05 18:46:02 +00:00
|
|
|
|
2012-07-01 16:14:02 +00:00
|
|
|
/*
|
|
|
|
* Remaining header entries: size of compressed index and size of
|
|
|
|
* compressed data are inserted later via rabin_update_hdr, after actual compression!
|
|
|
|
*/
|
|
|
|
return (rabin_index_sz);
|
2012-06-29 12:53:55 +00:00
|
|
|
}
|
|
|
|
}
|
2012-07-01 16:14:02 +00:00
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
rabin_update_hdr(uchar_t *buf, ssize_t rabin_index_sz_cmp, ssize_t rabin_data_sz_cmp)
|
|
|
|
{
|
|
|
|
ssize_t *entries;
|
|
|
|
|
2012-07-03 17:17:24 +00:00
|
|
|
buf += sizeof (uint32_t);
|
2012-07-01 16:14:02 +00:00
|
|
|
entries = (ssize_t *)buf;
|
|
|
|
entries[1] = htonll(rabin_index_sz_cmp);
|
|
|
|
entries[3] = htonll(rabin_data_sz_cmp);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2012-07-03 17:17:24 +00:00
|
|
|
rabin_parse_hdr(uchar_t *buf, uint32_t *blknum, ssize_t *rabin_index_sz,
|
2012-07-01 16:14:02 +00:00
|
|
|
ssize_t *rabin_data_sz, ssize_t *rabin_index_sz_cmp,
|
|
|
|
ssize_t *rabin_data_sz_cmp, ssize_t *rabin_deduped_size)
|
|
|
|
{
|
|
|
|
ssize_t *entries;
|
|
|
|
|
2012-07-03 17:17:24 +00:00
|
|
|
*blknum = ntohl(*((uint32_t *)(buf)));
|
|
|
|
buf += sizeof (uint32_t);
|
2012-07-01 16:14:02 +00:00
|
|
|
|
|
|
|
entries = (ssize_t *)buf;
|
|
|
|
*rabin_data_sz = ntohll(entries[0]);
|
|
|
|
*rabin_index_sz = (ssize_t)(*blknum) * RABIN_ENTRY_SIZE;
|
|
|
|
*rabin_index_sz_cmp = ntohll(entries[1]);
|
|
|
|
*rabin_deduped_size = ntohll(entries[2]);
|
|
|
|
*rabin_data_sz_cmp = ntohll(entries[3]);
|
2012-06-29 12:53:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
rabin_inverse_dedup(rabin_context_t *ctx, uchar_t *buf, ssize_t *size)
|
|
|
|
{
|
2012-07-03 17:17:24 +00:00
|
|
|
uint32_t blknum, blk, oblk, len;
|
|
|
|
uint32_t *rabin_index;
|
2012-07-01 16:14:02 +00:00
|
|
|
ssize_t data_sz, sz, indx_cmp, data_sz_cmp, deduped_sz;
|
|
|
|
ssize_t rabin_index_sz, pos1, i;
|
2012-06-29 12:53:55 +00:00
|
|
|
uchar_t *pos2;
|
|
|
|
|
2012-07-01 16:14:02 +00:00
|
|
|
rabin_parse_hdr(buf, &blknum, &rabin_index_sz, &data_sz, &indx_cmp, &data_sz_cmp, &deduped_sz);
|
2012-07-03 17:17:24 +00:00
|
|
|
rabin_index = (uint32_t *)(buf + RABIN_HDR_SIZE);
|
2012-07-01 16:14:02 +00:00
|
|
|
pos1 = rabin_index_sz + RABIN_HDR_SIZE;
|
2012-06-29 12:53:55 +00:00
|
|
|
pos2 = ctx->cbuf;
|
|
|
|
sz = 0;
|
|
|
|
ctx->valid = 1;
|
|
|
|
|
|
|
|
for (blk = 0; blk < blknum; blk++) {
|
2012-07-01 16:14:02 +00:00
|
|
|
len = ntohl(rabin_index[blk]);
|
2012-06-29 18:15:06 +00:00
|
|
|
if (len == 0) {
|
|
|
|
ctx->blocks[blk].length = 0;
|
|
|
|
ctx->blocks[blk].index = 0;
|
|
|
|
|
2012-07-02 16:38:03 +00:00
|
|
|
} else if (!(len & RABIN_INDEX_FLAG)) {
|
2012-06-29 12:53:55 +00:00
|
|
|
ctx->blocks[blk].length = len;
|
|
|
|
ctx->blocks[blk].offset = pos1;
|
|
|
|
pos1 += len;
|
|
|
|
} else {
|
2012-07-19 16:11:07 +00:00
|
|
|
bsize_t blen;
|
|
|
|
|
2012-06-29 12:53:55 +00:00
|
|
|
ctx->blocks[blk].length = 0;
|
2012-07-19 16:11:07 +00:00
|
|
|
if (len & GET_SIMILARITY_FLAG) {
|
|
|
|
ctx->blocks[blk].offset = pos1;
|
|
|
|
ctx->blocks[blk].index = (len & RABIN_INDEX_VALUE) | SET_SIMILARITY_FLAG;
|
|
|
|
blen = get_bsdiff_sz(buf + pos1);
|
|
|
|
pos1 += blen;
|
|
|
|
} else {
|
|
|
|
ctx->blocks[blk].index = len & RABIN_INDEX_VALUE;
|
|
|
|
}
|
2012-06-29 12:53:55 +00:00
|
|
|
}
|
|
|
|
}
|
2012-07-19 16:11:07 +00:00
|
|
|
|
2012-06-29 12:53:55 +00:00
|
|
|
for (blk = 0; blk < blknum; blk++) {
|
2012-07-19 16:11:07 +00:00
|
|
|
int rv;
|
|
|
|
bsize_t newsz;
|
|
|
|
|
2012-06-29 18:15:06 +00:00
|
|
|
if (ctx->blocks[blk].length == 0 && ctx->blocks[blk].index == 0) continue;
|
2012-06-29 12:53:55 +00:00
|
|
|
if (ctx->blocks[blk].length > 0) {
|
|
|
|
len = ctx->blocks[blk].length;
|
|
|
|
pos1 = ctx->blocks[blk].offset;
|
|
|
|
} else {
|
|
|
|
oblk = ctx->blocks[blk].index;
|
2012-07-19 16:11:07 +00:00
|
|
|
|
|
|
|
if (oblk & GET_SIMILARITY_FLAG) {
|
|
|
|
oblk = oblk & CLEAR_SIMILARITY_FLAG;
|
|
|
|
len = ctx->blocks[oblk].length;
|
|
|
|
pos1 = ctx->blocks[oblk].offset;
|
|
|
|
newsz = data_sz - sz;
|
|
|
|
rv = bspatch(buf + ctx->blocks[blk].offset, buf + pos1, len, pos2, &newsz);
|
|
|
|
if (rv == 0) {
|
|
|
|
fprintf(stderr, "Failed to bspatch block.\n");
|
|
|
|
ctx->valid = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
pos2 += newsz;
|
|
|
|
sz += newsz;
|
|
|
|
if (sz > data_sz) {
|
|
|
|
fprintf(stderr, "Dedup data overflows chunk.\n");
|
|
|
|
ctx->valid = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
len = ctx->blocks[oblk].length;
|
|
|
|
pos1 = ctx->blocks[oblk].offset;
|
|
|
|
}
|
2012-06-29 12:53:55 +00:00
|
|
|
}
|
|
|
|
memcpy(pos2, buf + pos1, len);
|
|
|
|
pos2 += len;
|
|
|
|
sz += len;
|
2012-07-01 16:14:02 +00:00
|
|
|
if (sz > data_sz) {
|
2012-07-19 16:11:07 +00:00
|
|
|
fprintf(stderr, "Dedup data overflows chunk.\n");
|
2012-06-29 12:53:55 +00:00
|
|
|
ctx->valid = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2012-07-01 16:14:02 +00:00
|
|
|
if (ctx->valid && sz < data_sz) {
|
2012-07-19 16:11:07 +00:00
|
|
|
fprintf(stderr, "Too little dedup data processed.\n");
|
2012-06-29 12:53:55 +00:00
|
|
|
ctx->valid = 0;
|
|
|
|
}
|
2012-07-01 16:14:02 +00:00
|
|
|
*size = data_sz;
|
2012-06-21 14:57:05 +00:00
|
|
|
}
|
|
|
|
|
2012-07-01 16:14:02 +00:00
|
|
|
/*
|
|
|
|
* TODO: Consolidate rabin dedup and compression/decompression in functions here rather than
|
|
|
|
* messy code in main program.
|
|
|
|
int
|
|
|
|
rabin_compress(rabin_context_t *ctx, uchar_t *from, ssize_t fromlen, uchar_t *to, ssize_t *tolen,
|
|
|
|
int level, char chdr, void *data, compress_func_ptr cmp)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
*/
|