mirror of https://github.com/borgbackup/borg.git
375 lines
9.8 KiB
C
375 lines
9.8 KiB
C
/*
|
|
* Borg cache synchronizer,
|
|
* based on a MessagePack for Python unpacking routine
|
|
*
|
|
* Copyright (C) 2009 Naoki INADA
|
|
* Copyright (c) 2017 Marian Beermann
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/*
|
|
* This limits the depth of the structures we can unpack, i.e. how many containers
|
|
* are nestable.
|
|
*/
|
|
#define MSGPACK_EMBED_STACK_SIZE (16)
|
|
#include "unpack_define.h"
|
|
|
|
// 2**32 - 1025
|
|
#define _MAX_VALUE ( (uint32_t) 4294966271 )
|
|
|
|
#define MIN(x, y) ((x) < (y) ? (x): (y))
|
|
|
|
#ifdef DEBUG
|
|
#define set_last_error(msg) \
|
|
fprintf(stderr, "cache_sync parse error: %s\n", (msg)); \
|
|
u->last_error = (msg);
|
|
#else
|
|
#define set_last_error(msg) \
|
|
u->last_error = (msg);
|
|
#endif
|
|
|
|
typedef struct unpack_user {
|
|
/* Item.chunks is at the top level; we don't care about anything else,
|
|
* only need to track the current level to navigate arbitrary and unknown structure.
|
|
* To discern keys from everything else on the top level we use expect_map_item_end.
|
|
*/
|
|
int level;
|
|
|
|
const char *last_error;
|
|
|
|
HashIndex *chunks;
|
|
|
|
/*
|
|
* We don't care about most stuff. This flag tells us whether we're at the chunks structure,
|
|
* meaning:
|
|
* {'foo': 'bar', 'chunks': [...], 'stuff': ... }
|
|
* ^-HERE-^
|
|
*/
|
|
int inside_chunks;
|
|
enum {
|
|
/* the next thing is a map key at the Item root level,
|
|
* and it might be the "chunks" key we're looking for */
|
|
expect_chunks_map_key,
|
|
|
|
/* blocking state to expect_chunks_map_key
|
|
* { 'stuff': <complex and arbitrary structure>, 'chunks': [
|
|
* ecmk -> emie -> -> -> -> ecmk ecb eeboce
|
|
* (nested containers are tracked via level)
|
|
* ecmk=expect_chunks_map_key, emie=expect_map_item_end, ecb=expect_chunks_begin,
|
|
* eeboce=expect_entry_begin_or_chunks_end
|
|
*/
|
|
expect_map_item_end,
|
|
|
|
/* next thing must be the chunks array (array) */
|
|
expect_chunks_begin,
|
|
|
|
/* next thing must either be another CLE (array) or end of Item.chunks (array_end) */
|
|
expect_entry_begin_or_chunks_end,
|
|
|
|
/*
|
|
* processing ChunkListEntry tuple:
|
|
* expect_key, expect_size, expect_csize, expect_entry_end
|
|
*/
|
|
/* next thing must be the key (raw, l=32) */
|
|
expect_key,
|
|
/* next thing must be the size (int) */
|
|
expect_size,
|
|
/* next thing must be the csize (int) */
|
|
expect_csize,
|
|
/* next thing must be the end of the CLE (array_end) */
|
|
expect_entry_end,
|
|
} expect;
|
|
|
|
struct {
|
|
char key[32];
|
|
uint32_t csize;
|
|
uint32_t size;
|
|
} current;
|
|
} unpack_user;
|
|
|
|
struct unpack_context;
|
|
typedef struct unpack_context unpack_context;
|
|
typedef int (*execute_fn)(unpack_context *ctx, const char* data, size_t len, size_t* off);
|
|
|
|
#define unexpected(what) \
|
|
if(u->inside_chunks || u->expect == expect_chunks_map_key) { \
|
|
set_last_error("Unexpected object: " what); \
|
|
return -1; \
|
|
}
|
|
|
|
static inline int unpack_callback_int64(unpack_user* u, int64_t d)
|
|
{
|
|
switch(u->expect) {
|
|
case expect_size:
|
|
u->current.size = d;
|
|
u->expect = expect_csize;
|
|
break;
|
|
case expect_csize:
|
|
u->current.csize = d;
|
|
u->expect = expect_entry_end;
|
|
break;
|
|
default:
|
|
unexpected("integer");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline int unpack_callback_uint16(unpack_user* u, uint16_t d)
|
|
{
|
|
return unpack_callback_int64(u, d);
|
|
}
|
|
|
|
static inline int unpack_callback_uint8(unpack_user* u, uint8_t d)
|
|
{
|
|
return unpack_callback_int64(u, d);
|
|
}
|
|
|
|
|
|
static inline int unpack_callback_uint32(unpack_user* u, uint32_t d)
|
|
{
|
|
return unpack_callback_int64(u, d);
|
|
}
|
|
|
|
static inline int unpack_callback_uint64(unpack_user* u, uint64_t d)
|
|
{
|
|
return unpack_callback_int64(u, d);
|
|
}
|
|
|
|
static inline int unpack_callback_int32(unpack_user* u, int32_t d)
|
|
{
|
|
return unpack_callback_int64(u, d);
|
|
}
|
|
|
|
static inline int unpack_callback_int16(unpack_user* u, int16_t d)
|
|
{
|
|
return unpack_callback_int64(u, d);
|
|
}
|
|
|
|
static inline int unpack_callback_int8(unpack_user* u, int8_t d)
|
|
{
|
|
return unpack_callback_int64(u, d);
|
|
}
|
|
|
|
/* Ain't got anything to do with those floats */
|
|
static inline int unpack_callback_double(unpack_user* u, double d)
|
|
{
|
|
(void)d;
|
|
unexpected("double");
|
|
return 0;
|
|
}
|
|
|
|
static inline int unpack_callback_float(unpack_user* u, float d)
|
|
{
|
|
(void)d;
|
|
unexpected("float");
|
|
return 0;
|
|
}
|
|
|
|
/* nil/true/false — I/don't/care */
|
|
static inline int unpack_callback_nil(unpack_user* u)
|
|
{
|
|
unexpected("nil");
|
|
return 0;
|
|
}
|
|
|
|
static inline int unpack_callback_true(unpack_user* u)
|
|
{
|
|
unexpected("true");
|
|
return 0;
|
|
}
|
|
|
|
static inline int unpack_callback_false(unpack_user* u)
|
|
{
|
|
unexpected("false");
|
|
return 0;
|
|
}
|
|
|
|
static inline int unpack_callback_array(unpack_user* u, unsigned int n)
|
|
{
|
|
switch(u->expect) {
|
|
case expect_chunks_begin:
|
|
/* b'chunks': [
|
|
* ^ */
|
|
u->expect = expect_entry_begin_or_chunks_end;
|
|
break;
|
|
case expect_entry_begin_or_chunks_end:
|
|
/* b'chunks': [ (
|
|
* ^ */
|
|
if(n != 3) {
|
|
set_last_error("Invalid chunk list entry length");
|
|
return -1;
|
|
}
|
|
u->expect = expect_key;
|
|
break;
|
|
default:
|
|
if(u->inside_chunks) {
|
|
set_last_error("Unexpected array start");
|
|
return -1;
|
|
} else {
|
|
u->level++;
|
|
return 0;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline int unpack_callback_array_item(unpack_user* u, unsigned int current)
|
|
{
|
|
(void)u; (void)current;
|
|
return 0;
|
|
}
|
|
|
|
static inline int unpack_callback_array_end(unpack_user* u)
|
|
{
|
|
uint32_t *cache_entry;
|
|
uint32_t cache_values[3];
|
|
uint64_t refcount;
|
|
|
|
switch(u->expect) {
|
|
case expect_entry_end:
|
|
/* b'chunks': [ ( b'1234...', 123, 345 )
|
|
* ^ */
|
|
cache_entry = (uint32_t*) hashindex_get(u->chunks, u->current.key);
|
|
if (cache_entry) {
|
|
refcount = _le32toh(cache_entry[0]);
|
|
refcount += 1;
|
|
cache_entry[0] = _htole32(MIN(refcount, _MAX_VALUE));
|
|
} else {
|
|
/* refcount, size, csize */
|
|
cache_values[0] = _htole32(1);
|
|
cache_values[1] = _htole32(u->current.size);
|
|
cache_values[2] = _htole32(u->current.csize);
|
|
if (!hashindex_set(u->chunks, u->current.key, cache_values)) {
|
|
set_last_error("hashindex_set failed");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
u->expect = expect_entry_begin_or_chunks_end;
|
|
break;
|
|
case expect_entry_begin_or_chunks_end:
|
|
/* b'chunks': [ ]
|
|
* ^ */
|
|
/* end of Item.chunks */
|
|
u->inside_chunks = 0;
|
|
u->expect = expect_map_item_end;
|
|
break;
|
|
default:
|
|
if(u->inside_chunks) {
|
|
set_last_error("Invalid state transition (unexpected array end)");
|
|
return -1;
|
|
} else {
|
|
u->level--;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int unpack_callback_map(unpack_user* u, unsigned int n)
|
|
{
|
|
(void)n;
|
|
|
|
|
|
if(u->level == 0) {
|
|
/* This begins a new Item */
|
|
u->expect = expect_chunks_map_key;
|
|
}
|
|
|
|
if(u->inside_chunks) {
|
|
unexpected("map");
|
|
}
|
|
|
|
u->level++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int unpack_callback_map_item(unpack_user* u, unsigned int current)
|
|
{
|
|
(void)u; (void)current;
|
|
if(u->level == 1) {
|
|
switch(u->expect) {
|
|
case expect_map_item_end:
|
|
u->expect = expect_chunks_map_key;
|
|
break;
|
|
default:
|
|
set_last_error("Unexpected map item");
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline int unpack_callback_map_end(unpack_user* u)
|
|
{
|
|
u->level--;
|
|
if(u->inside_chunks) {
|
|
set_last_error("Unexpected map end");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline int unpack_callback_raw(unpack_user* u, const char* b, const char* p, unsigned int l)
|
|
{
|
|
/* raw = what Borg uses for binary stuff and strings as well */
|
|
/* Note: p points to an internal buffer which contains l bytes. */
|
|
(void)b;
|
|
|
|
switch(u->expect) {
|
|
case expect_key:
|
|
if(l != 32) {
|
|
set_last_error("Incorrect key length");
|
|
return -1;
|
|
}
|
|
memcpy(u->current.key, p, 32);
|
|
u->expect = expect_size;
|
|
break;
|
|
case expect_chunks_map_key:
|
|
if(l == 6 && !memcmp("chunks", p, 6)) {
|
|
u->expect = expect_chunks_begin;
|
|
u->inside_chunks = 1;
|
|
} else {
|
|
u->expect = expect_map_item_end;
|
|
}
|
|
break;
|
|
default:
|
|
if(u->inside_chunks) {
|
|
set_last_error("Unexpected bytes in chunks structure");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int unpack_callback_bin(unpack_user* u, const char* b, const char* p, unsigned int l)
|
|
{
|
|
(void)u; (void)b; (void)p; (void)l;
|
|
unexpected("bin");
|
|
return 0;
|
|
}
|
|
|
|
static inline int unpack_callback_ext(unpack_user* u, const char* base, const char* pos,
|
|
unsigned int length)
|
|
{
|
|
(void)u; (void)base; (void)pos; (void)length;
|
|
unexpected("ext");
|
|
return 0;
|
|
}
|
|
|
|
#include "unpack_template.h"
|