/*
* Library routines
*
* Copyright (C) 2013 Craig Barratt.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, visit the http://fsf.org website.
*/
#include "backuppc.h"
char BPC_TopDir[BPC_MAXPATHLEN];
char BPC_PoolDir[BPC_MAXPATHLEN];
char BPC_CPoolDir[BPC_MAXPATHLEN];
char BPC_PoolDir3[BPC_MAXPATHLEN];
char BPC_CPoolDir3[BPC_MAXPATHLEN];
int BPC_HardLinkMax = 32000;
int BPC_PoolV3Enabled = 0;
int BPC_TmpFileUnique = -1;
int BPC_LogLevel = 0;
static char *hexDigits = "0123456789abcdef";
void bpc_lib_conf_init(char *topDir, int hardLinkMax, int poolV3Enabled, int logLevel)
{
if ( logLevel >= 8 ) bpc_logMsgf("bpc_lib_conf_init: topDir = %s, logLevel = %d\n", topDir, logLevel);
snprintf(BPC_TopDir, sizeof(BPC_TopDir), "%s", topDir);
snprintf(BPC_CPoolDir, sizeof(BPC_CPoolDir), "%s/%s", BPC_TopDir, "cpool");
snprintf(BPC_CPoolDir3, sizeof(BPC_CPoolDir3), "%s/%s", BPC_TopDir, "cpool");
snprintf(BPC_PoolDir, sizeof(BPC_PoolDir), "%s/%s", BPC_TopDir, "pool");
snprintf(BPC_PoolDir3, sizeof(BPC_PoolDir3), "%s/%s", BPC_TopDir, "pool");
BPC_HardLinkMax = hardLinkMax;
BPC_PoolV3Enabled = poolV3Enabled;
BPC_LogLevel = logLevel;
}
void bpc_lib_setTmpFileUnique(int val)
{
BPC_TmpFileUnique = val;
}
int bpc_lib_setLogLevel(int logLevel)
{
if ( logLevel >= 0 ) {
BPC_LogLevel = logLevel;
}
return BPC_LogLevel;
}
/*
* Converts a byte to two ascii hex digits at outStr[0] and outStr[1].
* Nothing is written at outStr[2]; ie: no NULL termination
*/
void bpc_byte2hex(char *outStr, int byte)
{
outStr[0] = hexDigits[(byte >> 4) & 0xf];
outStr[1] = hexDigits[(byte >> 0) & 0xf];
}
static uchar bpc_hexChar2nibble(char c)
{
if ( '0' <= c && c <= '9' ) return c - '0';
if ( 'A' <= c && c <= 'F' ) return 0xa + (c - 'A');
if ( 'a' <= c && c <= 'f' ) return 0xa + (c - 'a');
return 0;
}
uchar bpc_hexStr2byte(char c1, char c2)
{
return (bpc_hexChar2nibble(c1) << 4) | bpc_hexChar2nibble(c2);
}
void bpc_digest_buffer2MD5(bpc_digest *digest, uchar *buffer, size_t bufferLen)
{
md_context md5;
md5_begin(&md5);
md5_update(&md5, buffer, bufferLen);
md5_result(&md5, digest->digest);
digest->len = MD5_DIGEST_LEN;
}
void bpc_digest_append_ext(bpc_digest *digest, uint32 ext)
{
int i;
digest->len = 16;
if ( ext == 0 ) return;
for ( i = 24 ; i >= 0 ; i -= 8 ) {
if ( ext >= (1U << i) ) {
digest->digest[digest->len++] = (ext >> i) & 0xff;
}
}
}
/*
* returns 0 if the two digests are equal, non-zero if they are not
*/
int bpc_digest_compare(bpc_digest *digest1, bpc_digest *digest2)
{
if ( digest1->len != digest2->len ) return digest1->len - digest2->len;
return memcmp(digest1->digest, digest2->digest, digest1->len);
}
void bpc_digest_digest2str(bpc_digest *digest, char *hexStr)
{
int i;
char *out = hexStr;
for ( i = 0 ; i < digest->len ; i++ ) {
bpc_byte2hex(out, digest->digest[i]);
out += 2;
}
*out = '\0';
}
void bpc_digest_str2digest(bpc_digest *digest, char *hexStr)
{
for ( digest->len = 0 ; hexStr[0] && hexStr[1] && digest->len < BPC_DIGEST_LEN_MAX ; hexStr += 2 ) {
digest->digest[digest->len++] = bpc_hexStr2byte(hexStr[0], hexStr[1]);
}
}
void bpc_digest_md52path(char *path, int compress, bpc_digest *digest)
{
char *out;
/*
* MD5 digest of an empty file (ie, md5sum /dev/null)
*/
static uchar emptyFileMD5[] = {
0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04, 0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e
};
#if 0
/*
* Test code to create collisions in pool files.
* If you turn on this test, you should also force the zeroLenMD5
* digest comparison in bpc_poolWrite_write() to true.
*/
bpc_digest fixedDigest = *digest;
digest = &fixedDigest;
memcpy(digest->digest, emptyFileMD5, sizeof(emptyFileMD5));
#endif
if ( digest->len == sizeof(emptyFileMD5) && !memcmp(digest->digest, emptyFileMD5, sizeof(emptyFileMD5)) ) {
strcpy(path, "/dev/null");
return;
}
strncpy(path, compress ? BPC_CPoolDir : BPC_PoolDir, BPC_MAXPATHLEN - 32);
path[BPC_MAXPATHLEN - 48] = '\0';
out = path + strlen(path);
*out++ = '/';
bpc_byte2hex(out, digest->digest[0] & 0xfe); out += 2;
*out++ = '/';
bpc_byte2hex(out, digest->digest[1] & 0xfe); out += 2;
*out++ = '/';
bpc_digest_digest2str(digest, out);
}
void bpc_digest_md52path_v3(char *path, int compress, bpc_digest *digest)
{
int i;
char hexStr[BPC_DIGEST_LEN_MAX * 2 + 1];
uint32 ext = 0;
char n0 = hexDigits[(digest->digest[0] >> 4) & 0xf];
char n1 = hexDigits[(digest->digest[0] >> 0) & 0xf];
char n2 = hexDigits[(digest->digest[1] >> 4) & 0xf];
bpc_digest_digest2str(digest, hexStr);
for ( i = 16 ; i < digest->len ; i++ ) {
ext |= digest->digest[i - 16] << (8 * (i - 16));
}
if ( ext > 0 ) {
snprintf(path, BPC_MAXPATHLEN, "%s/%c/%c/%c/%s_%d", compress ? BPC_CPoolDir3 : BPC_PoolDir3, n0, n1, n2, hexStr, ext);
} else {
snprintf(path, BPC_MAXPATHLEN, "%s/%c/%c/%c/%s", compress ? BPC_CPoolDir3 : BPC_PoolDir3, n0, n1, n2, hexStr);
}
}
void bpc_digest_buffer2MD5_v3(bpc_digest *digest, uchar *buffer, size_t bufferLen)
{
char lenStr[256];
md_context md5;
md5_begin(&md5);
sprintf(lenStr, "%llu", (long long unsigned int)bufferLen);
md5_update(&md5, (uchar*)lenStr, strlen(lenStr));
if ( bufferLen > 262144 ) {
/*
* add the first and last 131072 bytes of the buffer,
* up to 1MB.
*/
int seekPosn = (bufferLen > 1048576 ? 1048576 : bufferLen) - 131072;
md5_update(&md5, buffer, 131072);
md5_update(&md5, buffer + seekPosn, 131072);
} else {
/*
* add the whole buffer
*/
md5_update(&md5, buffer, bufferLen);
}
md5_result(&md5, digest->digest);
digest->len = MD5_DIGEST_LEN;
}
static void bpc_fileNameEltMangle2(char *path, int pathSize, char *pathUM, int stopAtSlash)
{
if ( !*pathUM || (stopAtSlash && *pathUM == '/') ) {
*path = '\0';
return;
}
*path++ = 'f'; pathSize--;
for ( ; *pathUM && pathSize > 4 ; ) {
if ( stopAtSlash && *pathUM == '/' ) break;
if ( *pathUM != '%' && *pathUM != '/' && *pathUM != '\n' && *pathUM != '\r' ) {
*path++ = *pathUM++; pathSize--;
} else {
*path++ = '%'; pathSize--;
bpc_byte2hex(path, *pathUM++);
path += 2; pathSize -= 2;
}
}
*path = '\0';
}
void bpc_fileNameEltMangle(char *path, int pathSize, char *pathUM)
{
bpc_fileNameEltMangle2(path, pathSize, pathUM, 0);
}
void bpc_fileNameMangle(char *path, int pathSize, char *pathUM)
{
char *p;
for ( ; *pathUM && pathSize > 4 ; ) {
int len;
bpc_fileNameEltMangle2(path, pathSize, pathUM, 1);
len = strlen(path);
path += len; pathSize -= len;
if ( !(p = strchr(pathUM, '/')) ) break;
for ( pathUM = p + 1 ; *pathUM == '/' ; pathUM++ ) { }
if ( *pathUM ) {
*path++ = '/'; pathSize--;
}
}
*path = '\0';
}
/*
* Simple logging functions. If you register callbacks, they will be called.
* Otherwise, messages are accumulated, and a callback allows the
* log strings to be fetched.
*
* We store the data in a single buffer of '\0'-terminated strings.
*/
typedef struct {
char *mesg;
size_t mesgSize;
size_t mesgLen;
unsigned long errorCnt;
} LogMsgData;
static LogMsgData LogData;
static void (*LogMsgCB)(int, char *mesg, size_t mesgLen);
void bpc_logMsgf(char *fmt, ...)
{
int strLen, pad = 0;
va_list args;
va_start(args, fmt);
if ( !LogData.mesg ) {
LogData.mesgSize = 8192;
LogData.mesgLen = 0;
if ( !(LogData.mesg = malloc(LogData.mesgSize)) ) {
printf("bpc_logMessagef: panic: can't allocate %lu bytes\n", (unsigned long)LogData.mesgSize);
LogData.errorCnt++;
return;
}
}
if ( BPC_TmpFileUnique >= 0 ) pad = 2;
strLen = vsnprintf(LogData.mesg + LogData.mesgLen + pad, LogData.mesgSize - LogData.mesgLen - pad, fmt, args);
if ( strLen + 2 + pad + LogData.mesgLen > LogData.mesgSize ) {
LogData.mesgSize += LogData.mesgSize + strLen + 2 + pad;
if ( !(LogData.mesg = realloc(LogData.mesg, LogData.mesgSize)) ) {
printf("bpc_logMessagef: panic: can't realloc %lu bytes\n", (unsigned long)LogData.mesgSize);
LogData.errorCnt++;
return;
}
va_start(args, fmt);
strLen = vsnprintf(LogData.mesg + LogData.mesgLen + pad, LogData.mesgSize - LogData.mesgLen - pad, fmt, args);
va_end(args);
}
if ( strLen > 0 ) {
if ( pad ) {
LogData.mesg[LogData.mesgLen++] = BPC_TmpFileUnique ? 'G' : 'R';
LogData.mesg[LogData.mesgLen++] = ' ';
}
LogData.mesgLen += strLen + 1;
}
if ( LogMsgCB ) {
(*LogMsgCB)(0, LogData.mesg, LogData.mesgLen - 1);
LogData.mesgLen = 0;
}
va_end(args);
}
/*
* For now, this is just the same as bpc_logMsgf(). We can't call a common routine that
* does the work, since args might need to be parsed twice when the buffer is grown.
*/
void bpc_logErrf(char *fmt, ...)
{
int strLen, pad = 0;
va_list args;
va_start(args, fmt);
if ( !LogData.mesg ) {
LogData.mesgSize = 8192;
LogData.mesgLen = 0;
if ( !(LogData.mesg = malloc(LogData.mesgSize)) ) {
printf("bpc_logMessagef: panic: can't allocate %lu bytes\n", (unsigned long)LogData.mesgSize);
LogData.errorCnt++;
return;
}
}
if ( BPC_TmpFileUnique >= 0 ) pad = 2;
strLen = vsnprintf(LogData.mesg + LogData.mesgLen + pad, LogData.mesgSize - LogData.mesgLen - pad, fmt, args);
if ( strLen + 2 + pad + LogData.mesgLen > LogData.mesgSize ) {
LogData.mesgSize += LogData.mesgSize + strLen + 2 + pad;
if ( !(LogData.mesg = realloc(LogData.mesg, LogData.mesgSize)) ) {
printf("bpc_logMessagef: panic: can't realloc %lu bytes\n", (unsigned long)LogData.mesgSize);
LogData.errorCnt++;
return;
}
va_start(args, fmt);
strLen = vsnprintf(LogData.mesg + LogData.mesgLen + pad, LogData.mesgSize - LogData.mesgLen - pad, fmt, args);
va_end(args);
}
if ( strLen > 0 ) {
if ( pad ) {
LogData.mesg[LogData.mesgLen++] = BPC_TmpFileUnique ? 'G' : 'R';
LogData.mesg[LogData.mesgLen++] = ' ';
}
LogData.mesgLen += strLen + 1;
}
if ( LogMsgCB ) {
(*LogMsgCB)(0, LogData.mesg, LogData.mesgLen - 1);
LogData.mesgLen = 0;
}
va_end(args);
}
void bpc_logMsgGet(char **mesg, size_t *mesgLen)
{
*mesg = LogData.mesg;
*mesgLen = LogData.mesgLen;
LogData.mesgLen = 0;
}
void bpc_logMsgErrorCntGet(unsigned long *errorCnt)
{
*errorCnt = LogData.errorCnt;
LogData.errorCnt = 0;
}
void bpc_logMsgCBSet(void (*cb)(int errFlag, char *mesg, size_t mesgLen))
{
LogMsgCB = cb;
}