/*************************************************************************
* (C) 2010 AITNET ltd - Sofia/Bulgaria - <misho@aitbg.com>
* by Michael Pounov <misho@openbsd-bg.org>
*
* $Author: misho $
* $Id: aitsync.c,v 1.1 2010/03/24 16:00:15 misho Exp $
*
*************************************************************************/
#include "global.h"
#include "tool.h"
#include "zc.h"
#include "patch.h"
#include "file.h"
static int sync_Errno;
static char sync_Error[STRSIZ];
static inline int func_comp(sync_tag_t const *t1, sync_tag_t const *t2)
{
return t1->st_tag - t2->st_tag;
}
//
// Error maintenance functions ...
//
// sync_GetErrno() Get error code of last operation
inline int sync_GetErrno()
{
return sync_Errno;
}
// sync_GetError() Get error text of last operation
inline const char *sync_GetError()
{
return sync_Error;
}
// sync_SetErr() Set error to variables for internal use!!!
inline void syncSetErr(int eno, char *estr, ...)
{
va_list lst;
sync_Errno = eno;
memset(sync_Error, 0, STRSIZ);
va_start(lst, estr);
vsnprintf(sync_Error, STRSIZ, estr, lst);
va_end(lst);
}
// ----------------------------------------------------------
/*
* syncSignature() Calculate and create signature for diff
* @csInput = Input patched file name for calculating check sums
* @csSig = Output Signature file name
* return: -1 error, 0 ok
*/
int syncSignature(const char *csInput, const char *csSig)
{
int inf, outf, ret;
u_char buf[CHUNK_MAX];
register int i = 0;
off_t off = 0ll;
sync_chunk_t sc;
inf = syncOpen(csInput, O_RDONLY);
if (inf == -1)
return inf;
outf = syncOpen(csSig, O_WRONLY);
if (outf == -1) {
syncClose(inf);
return outf;
}
for (i = 0, off = 0ll, ret = -1; ret; i++, off += ret) {
memset(buf, 0, CHUNK_MAX);
ret = read(inf, buf, CHUNK_MAX);
if (ret == -1) {
SETERR;
break;
}
// fill chunk
sync_mksig(i, off, buf, ret, &sc);
if (write(outf, &sc, sizeof sc) == -1) {
SETERR;
break;
}
}
syncClose(outf);
syncClose(inf);
return ret;
}
/*
* syncDelta() Create Delta patch file
* @csInput = Input original source file name for make delta patch file
* @csSig = Input Signature file name
* @csDelta = Output Delta patch file name
* @compress = Compress output, 0 not compressed
* return: -1 error, 0 ok
*/
int syncDelta(const char *csInput, const char *csSig, const char *csDelta, int compress)
{
int inf, outf, f, sigf, ret, cnt;
size_t blk;
register int i, j, c, cx;
struct stat sb, sb_f;
u_long tags[TABLESIZ];
sync_tag_t *tag_table;
sync_chunk_t *chunks, *find, sc;
u_char buf[CHUNK_MAX];
off_t off;
char szTemp[MAXPATHLEN];
/* load signatures */
sigf = syncOpen(csSig, O_RDONLY);
if (sigf == -1) {
return sigf;
}
if (fstat(sigf, &sb) == -1) {
SETERR;
syncClose(sigf);
return -1;
} else {
if (!sb.st_size) {
syncClose(sigf);
return 1;
}
cnt = sb.st_size / sizeof(sync_chunk_t);
if (sb.st_size % sizeof(sync_chunk_t)) {
syncSetErr(ENOEXEC, "Error:: signature file is broken!\n");
syncClose(sigf);
return -1;
}
}
chunks = (sync_chunk_t*) mmap(0, sb.st_size, PROT_READ, MAP_PRIVATE, sigf, 0);
if (MAP_FAILED == chunks) {
SETERR;
syncClose(sigf);
return -1;
}
syncClose(sigf);
/* build from signatures sorted index and hashes */
// init tags array
for (i = 0; i < TABLESIZ; i++)
tags[i] = NULL_TAG;
// build index from signature blocks
tag_table = (sync_tag_t*) calloc(cnt, sizeof(sync_tag_t));
if (!tag_table) {
SETERR;
munmap(chunks, sb.st_size);
return -1;
} else {
for (i = 0; i < cnt; i++) {
tag_table[i].st_id = i;
tag_table[i].st_tag = GETTAG(chunks[i].sc_roll);
}
qsort(tag_table, cnt, sizeof(sync_tag_t), (int (*)(const void *, const void *)) func_comp);
}
// assign less id position in tag_table to tags
for (i = cnt - 1; i > -1; i--)
tags[tag_table[i].st_tag] = i;
/* build delta patch */
inf = syncOpen(csInput, O_RDONLY);
if (inf == -1) {
free(tag_table);
munmap(chunks, sb.st_size);
return inf;
}
if (compress)
f = syncTemp(szTemp, MAXPATHLEN);
else
f = syncOpen(csDelta, O_WRONLY);
if (f == -1) {
syncClose(inf);
free(tag_table);
munmap(chunks, sb.st_size);
return f;
}
for (i = 0, off = 0ll, ret = -1, blk = 0; (ret = read(inf, buf, CHUNK_MAX)); i++, off += ret) {
if (ret == -1) {
SETERR;
break;
}
find = NULL;
// printf("+ find=%p off=%llu i=%d blk=%d\n", find, off, i, blk);
// check chunk for differences with signature
sync_mksig(i, off, buf, ret, &sc);
cx = GETTAG(sc.sc_roll);
// find in hash -> hash_sorted_table
if (NULL_TAG != tags[cx] && tag_table[tags[cx]].st_tag == cx) {
// find in hash_sorted_table crc == -> real chunks id
for (j = 0, c = tag_table[tags[cx]].st_id; tag_table[tags[cx] + j].st_tag == cx;
j++, c = tag_table[tags[cx] + j].st_id) {
if (chunks[c].sc_magic == sc.sc_magic && chunks[c].sc_len == sc.sc_len &&
chunks[c].sc_roll == sc.sc_roll &&
!memcmp(chunks[c].sc_cksum, sc.sc_cksum, MD5_DIGEST_LENGTH)) {
find = &chunks[c];
break;
}
}
}
// printf("+ find=%p off=%llu i=%d blk=%d\n", find, off, i, blk);
// if match chunk, check for previous match
if (!blk && find)
continue;
// if not find chunk in signature skip write to delta patch
if (!find) {
/* different piece, write it! */
// write signature of current chunk
ret = write(f, &sc, sizeof sc);
if (-1 == ret) {
SETERR;
break;
}
// if write chunk len is differnt from requested len
if (ret != sizeof sc) {
syncSetErr(ENOEXEC, "Error:: delta file signature is broken!\n");
ret = -1;
break;
}
// write current chunk ...
ret = write(f, buf, sc.sc_len);
if (-1 == ret) {
SETERR;
break;
}
// if write chunk len is differnt from requested len
if (ret != sc.sc_len) {
syncSetErr(ENOEXEC, "Error:: delta file data is broken!\n");
ret = -1;
break;
}
blk += sc.sc_len;
continue;
}
// match 1st block after difference and copy signature from B
memcpy(&sc, find, sizeof sc);
sc.sc_magic = SIGSYNC_MAGIC;
sc.sc_len = blk;
// write signature from chunk B
blk = write(f, &sc, sizeof sc);
if (-1 == blk) {
SETERR;
break;
}
// if write chunk len is differnt from requested len
if (blk != sizeof sc) {
syncSetErr(ENOEXEC, "Error:: delta file end signature is broken!\n");
ret = -1;
break;
}
blk ^= blk;
}
// check for error or empty delta file
if (ret == -1)
goto end;
fsync(f);
if (fstat(f, &sb_f) == -1) {
SETERR;
ret = -1;
goto end;
}
// No deferences, not needed delta.patch !!!
if (!sb_f.st_size) {
ret = 1;
goto end;
}
/* Delta patch is READY */
// build compressed delta file
if (compress) {
outf = syncOpen(csDelta, O_WRONLY);
if (outf == -1) {
ret = outf;
goto end;
}
if (sync_Deflate(f, outf, Z_DEFAULT_COMPRESSION) == -1) {
syncClose(outf);
unlink(csDelta);
ret = -1;
goto end;
}
syncClose(outf);
}
end:
syncClose(f);
unlink(szTemp);
syncClose(inf);
free(tag_table);
munmap(chunks, sb.st_size);
return ret;
}
/*
* syncPatch() Apply delta patch file to target
* @csInput = Input target file name for patch
* @csDelta = Input Delta patch file name
* @csPatch = After applied patch create new alternate target file, if != NULL
* @compress = Compress output, 0 not compressed
* return: -1 error, 0 ok, create delta patch, 1 ok, no differences and not create patch
*/
int syncPatch(const char *csInput, const char *csDelta, const char *csPatch, int compress)
{
int inf, outf, f, d, ret, readlen;
char szTemp[MAXPATHLEN];
u_char *buffer, buf[CHUNK_MAX];
struct stat sb;
void *delta;
struct tagPiece *piece, *pieces = NULL;
register int i;
off_t off;
sync_chunk_t sc, *suffix;
if (compress) {
f = syncOpen(csDelta, O_RDONLY);
if (f == -1)
return f;
d = syncTemp(szTemp, MAXPATHLEN);
if (d == -1) {
syncClose(f);
return d;
}
if (sync_Inflate(f, d) == -1) {
syncClose(d);
syncClose(f);
unlink(szTemp);
return -1;
} else
syncClose(f);
} else {
d = syncOpen(csDelta, O_RDONLY);
if (d == -1)
return d;
}
if (fstat(d, &sb) == -1) {
SETERR;
syncClose(d);
if (compress)
unlink(szTemp);
return -1;
}
delta = mmap(0, sb.st_size, PROT_READ, MAP_PRIVATE, d, 0);
if (MAP_FAILED == delta) {
SETERR;
syncClose(d);
if (compress)
unlink(szTemp);
return -1;
} else {
syncClose(d);
if (compress)
unlink(szTemp);
}
if (sync_buildPatch(delta, sb.st_size, &pieces) == -1 || !pieces) {
syncSetErr(ENOEXEC, "Error:: patch file is broken!\n");
munmap(delta, sb.st_size);
return -1;
}
inf = syncOpen(csInput, O_RDONLY);
if (inf == -1) {
if (pieces)
free(pieces);
munmap(delta, sb.st_size);
return inf;
}
outf = syncOpen(csPatch, O_WRONLY);
if (outf == -1) {
syncClose(inf);
if (pieces)
free(pieces);
munmap(delta, sb.st_size);
return outf;
}
if (fstat(inf, &sb) == -1) {
SETERR;
ret = -1;
goto end;
} else {
if (!sb.st_size) {
ret = -1;
goto end;
}
}
ret = readlen = 0;
buffer = NULL;
for (i = 0, off = 0ll, suffix = NULL, piece = pieces; piece->pfx; i++, off += readlen) {
// printf("i=%d off=%llu sfx=%p piece=%p\n", i, off, suffix, piece);
// if input offset is less then input file size
if (off < sb.st_size) {
readlen = read(inf, buf, CHUNK_MAX);
if (readlen == -1) {
SETERR;
ret = -1;
break;
}
// if suffix find, check for correct patch
if (suffix) {
if (suffix->sc_len != readlen || suffix->sc_off != off) {
syncSetErr(ENOEXEC, "Error:: patch file is broken! (wrong suffix pos)\n");
ret = -1;
break;
}
sync_mksig(i, off, buf, readlen, &sc);
if (sc.sc_roll != suffix->sc_roll ||
memcmp(sc.sc_cksum, suffix->sc_cksum, MD5_DIGEST_LENGTH)) {
syncSetErr(ENOEXEC, "Error:: patch file is broken! (wrong suffix crc)\n");
ret = -1;
break;
}
suffix = NULL;
}
buffer = buf;
}
// printf("i=%d off=%llu sfx=%p piece=%p pfx=%p pfx_off=%llu\n", i, off, suffix, piece,
// piece ? piece->pfx : 0l, piece->pfx ? piece->pfx->sc_off : 0l);
// if delta chunk match!
if (piece->pfx && piece->pfx->sc_off == off) {
if (!piece->buf) {
syncSetErr(ENOEXEC, "Error:: patch file is broken! (missing data)\n");
ret = -1;
break;
}
buffer = piece->buf;
readlen = piece->pfx->sc_len;
suffix = piece->sfx ? piece->sfx : NULL;
piece++;
if (suffix && off >= sb.st_size) {
syncSetErr(ENOEXEC, "Error:: patch file is broken! (after eof find suffix)\n");
ret = -1;
break;
}
} else
if (off >= sb.st_size) {
if (piece->pfx) {
syncSetErr(ENOEXEC, "Error:: patch file is broken! (after eof find prefix)\n");
ret = -1;
}
break;
}
ret = write(outf, buffer, readlen);
if (ret == -1 || ret != readlen) {
SETERR;
break;
}
}
end:
syncClose(inf);
syncClose(outf);
if (pieces)
free(pieces);
munmap(delta, sb.st_size);
return ret;
}
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>