You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1314 lines
32 KiB
1314 lines
32 KiB
/* |
|
Copyright 2020 Google LLC |
|
|
|
Use of this source code is governed by a BSD-style |
|
license that can be found in the LICENSE file or at |
|
https://developers.google.com/open-source/licenses/bsd |
|
*/ |
|
|
|
/* record.c - methods for different types of records. */ |
|
|
|
#include "record.h" |
|
|
|
#include "system.h" |
|
#include "constants.h" |
|
#include "reftable-error.h" |
|
#include "basics.h" |
|
|
|
static struct reftable_record_vtable * |
|
reftable_record_vtable(struct reftable_record *rec); |
|
static void *reftable_record_data(struct reftable_record *rec); |
|
|
|
int get_var_int(uint64_t *dest, struct string_view *in) |
|
{ |
|
int ptr = 0; |
|
uint64_t val; |
|
|
|
if (in->len == 0) |
|
return -1; |
|
val = in->buf[ptr] & 0x7f; |
|
|
|
while (in->buf[ptr] & 0x80) { |
|
ptr++; |
|
if (ptr > in->len) { |
|
return -1; |
|
} |
|
val = (val + 1) << 7 | (uint64_t)(in->buf[ptr] & 0x7f); |
|
} |
|
|
|
*dest = val; |
|
return ptr + 1; |
|
} |
|
|
|
int put_var_int(struct string_view *dest, uint64_t val) |
|
{ |
|
uint8_t buf[10] = { 0 }; |
|
int i = 9; |
|
int n = 0; |
|
buf[i] = (uint8_t)(val & 0x7f); |
|
i--; |
|
while (1) { |
|
val >>= 7; |
|
if (!val) { |
|
break; |
|
} |
|
val--; |
|
buf[i] = 0x80 | (uint8_t)(val & 0x7f); |
|
i--; |
|
} |
|
|
|
n = sizeof(buf) - i - 1; |
|
if (dest->len < n) |
|
return -1; |
|
memcpy(dest->buf, &buf[i + 1], n); |
|
return n; |
|
} |
|
|
|
int reftable_is_block_type(uint8_t typ) |
|
{ |
|
switch (typ) { |
|
case BLOCK_TYPE_REF: |
|
case BLOCK_TYPE_LOG: |
|
case BLOCK_TYPE_OBJ: |
|
case BLOCK_TYPE_INDEX: |
|
return 1; |
|
} |
|
return 0; |
|
} |
|
|
|
uint8_t *reftable_ref_record_val1(const struct reftable_ref_record *rec) |
|
{ |
|
switch (rec->value_type) { |
|
case REFTABLE_REF_VAL1: |
|
return rec->value.val1; |
|
case REFTABLE_REF_VAL2: |
|
return rec->value.val2.value; |
|
default: |
|
return NULL; |
|
} |
|
} |
|
|
|
uint8_t *reftable_ref_record_val2(const struct reftable_ref_record *rec) |
|
{ |
|
switch (rec->value_type) { |
|
case REFTABLE_REF_VAL2: |
|
return rec->value.val2.target_value; |
|
default: |
|
return NULL; |
|
} |
|
} |
|
|
|
static int decode_string(struct strbuf *dest, struct string_view in) |
|
{ |
|
int start_len = in.len; |
|
uint64_t tsize = 0; |
|
int n = get_var_int(&tsize, &in); |
|
if (n <= 0) |
|
return -1; |
|
string_view_consume(&in, n); |
|
if (in.len < tsize) |
|
return -1; |
|
|
|
strbuf_reset(dest); |
|
strbuf_add(dest, in.buf, tsize); |
|
string_view_consume(&in, tsize); |
|
|
|
return start_len - in.len; |
|
} |
|
|
|
static int encode_string(char *str, struct string_view s) |
|
{ |
|
struct string_view start = s; |
|
int l = strlen(str); |
|
int n = put_var_int(&s, l); |
|
if (n < 0) |
|
return -1; |
|
string_view_consume(&s, n); |
|
if (s.len < l) |
|
return -1; |
|
memcpy(s.buf, str, l); |
|
string_view_consume(&s, l); |
|
|
|
return start.len - s.len; |
|
} |
|
|
|
int reftable_encode_key(int *restart, struct string_view dest, |
|
struct strbuf prev_key, struct strbuf key, |
|
uint8_t extra) |
|
{ |
|
struct string_view start = dest; |
|
int prefix_len = common_prefix_size(&prev_key, &key); |
|
uint64_t suffix_len = key.len - prefix_len; |
|
int n = put_var_int(&dest, (uint64_t)prefix_len); |
|
if (n < 0) |
|
return -1; |
|
string_view_consume(&dest, n); |
|
|
|
*restart = (prefix_len == 0); |
|
|
|
n = put_var_int(&dest, suffix_len << 3 | (uint64_t)extra); |
|
if (n < 0) |
|
return -1; |
|
string_view_consume(&dest, n); |
|
|
|
if (dest.len < suffix_len) |
|
return -1; |
|
memcpy(dest.buf, key.buf + prefix_len, suffix_len); |
|
string_view_consume(&dest, suffix_len); |
|
|
|
return start.len - dest.len; |
|
} |
|
|
|
int reftable_decode_key(struct strbuf *key, uint8_t *extra, |
|
struct strbuf last_key, struct string_view in) |
|
{ |
|
int start_len = in.len; |
|
uint64_t prefix_len = 0; |
|
uint64_t suffix_len = 0; |
|
int n = get_var_int(&prefix_len, &in); |
|
if (n < 0) |
|
return -1; |
|
string_view_consume(&in, n); |
|
|
|
if (prefix_len > last_key.len) |
|
return -1; |
|
|
|
n = get_var_int(&suffix_len, &in); |
|
if (n <= 0) |
|
return -1; |
|
string_view_consume(&in, n); |
|
|
|
*extra = (uint8_t)(suffix_len & 0x7); |
|
suffix_len >>= 3; |
|
|
|
if (in.len < suffix_len) |
|
return -1; |
|
|
|
strbuf_reset(key); |
|
strbuf_add(key, last_key.buf, prefix_len); |
|
strbuf_add(key, in.buf, suffix_len); |
|
string_view_consume(&in, suffix_len); |
|
|
|
return start_len - in.len; |
|
} |
|
|
|
static void reftable_ref_record_key(const void *r, struct strbuf *dest) |
|
{ |
|
const struct reftable_ref_record *rec = |
|
(const struct reftable_ref_record *)r; |
|
strbuf_reset(dest); |
|
strbuf_addstr(dest, rec->refname); |
|
} |
|
|
|
static void reftable_ref_record_copy_from(void *rec, const void *src_rec, |
|
int hash_size) |
|
{ |
|
struct reftable_ref_record *ref = rec; |
|
const struct reftable_ref_record *src = src_rec; |
|
assert(hash_size > 0); |
|
|
|
/* This is simple and correct, but we could probably reuse the hash |
|
* fields. */ |
|
reftable_ref_record_release(ref); |
|
if (src->refname) { |
|
ref->refname = xstrdup(src->refname); |
|
} |
|
ref->update_index = src->update_index; |
|
ref->value_type = src->value_type; |
|
switch (src->value_type) { |
|
case REFTABLE_REF_DELETION: |
|
break; |
|
case REFTABLE_REF_VAL1: |
|
ref->value.val1 = reftable_malloc(hash_size); |
|
memcpy(ref->value.val1, src->value.val1, hash_size); |
|
break; |
|
case REFTABLE_REF_VAL2: |
|
ref->value.val2.value = reftable_malloc(hash_size); |
|
memcpy(ref->value.val2.value, src->value.val2.value, hash_size); |
|
ref->value.val2.target_value = reftable_malloc(hash_size); |
|
memcpy(ref->value.val2.target_value, |
|
src->value.val2.target_value, hash_size); |
|
break; |
|
case REFTABLE_REF_SYMREF: |
|
ref->value.symref = xstrdup(src->value.symref); |
|
break; |
|
} |
|
} |
|
|
|
static char hexdigit(int c) |
|
{ |
|
if (c <= 9) |
|
return '0' + c; |
|
return 'a' + (c - 10); |
|
} |
|
|
|
static void hex_format(char *dest, uint8_t *src, int hash_size) |
|
{ |
|
assert(hash_size > 0); |
|
if (src) { |
|
int i = 0; |
|
for (i = 0; i < hash_size; i++) { |
|
dest[2 * i] = hexdigit(src[i] >> 4); |
|
dest[2 * i + 1] = hexdigit(src[i] & 0xf); |
|
} |
|
dest[2 * hash_size] = 0; |
|
} |
|
} |
|
|
|
static void reftable_ref_record_print_sz(const struct reftable_ref_record *ref, |
|
int hash_size) |
|
{ |
|
char hex[GIT_MAX_HEXSZ + 1] = { 0 }; /* BUG */ |
|
printf("ref{%s(%" PRIu64 ") ", ref->refname, ref->update_index); |
|
switch (ref->value_type) { |
|
case REFTABLE_REF_SYMREF: |
|
printf("=> %s", ref->value.symref); |
|
break; |
|
case REFTABLE_REF_VAL2: |
|
hex_format(hex, ref->value.val2.value, hash_size); |
|
printf("val 2 %s", hex); |
|
hex_format(hex, ref->value.val2.target_value, |
|
hash_size); |
|
printf("(T %s)", hex); |
|
break; |
|
case REFTABLE_REF_VAL1: |
|
hex_format(hex, ref->value.val1, hash_size); |
|
printf("val 1 %s", hex); |
|
break; |
|
case REFTABLE_REF_DELETION: |
|
printf("delete"); |
|
break; |
|
} |
|
printf("}\n"); |
|
} |
|
|
|
void reftable_ref_record_print(const struct reftable_ref_record *ref, |
|
uint32_t hash_id) { |
|
reftable_ref_record_print_sz(ref, hash_size(hash_id)); |
|
} |
|
|
|
static void reftable_ref_record_release_void(void *rec) |
|
{ |
|
reftable_ref_record_release(rec); |
|
} |
|
|
|
void reftable_ref_record_release(struct reftable_ref_record *ref) |
|
{ |
|
switch (ref->value_type) { |
|
case REFTABLE_REF_SYMREF: |
|
reftable_free(ref->value.symref); |
|
break; |
|
case REFTABLE_REF_VAL2: |
|
reftable_free(ref->value.val2.target_value); |
|
reftable_free(ref->value.val2.value); |
|
break; |
|
case REFTABLE_REF_VAL1: |
|
reftable_free(ref->value.val1); |
|
break; |
|
case REFTABLE_REF_DELETION: |
|
break; |
|
default: |
|
abort(); |
|
} |
|
|
|
reftable_free(ref->refname); |
|
memset(ref, 0, sizeof(struct reftable_ref_record)); |
|
} |
|
|
|
static uint8_t reftable_ref_record_val_type(const void *rec) |
|
{ |
|
const struct reftable_ref_record *r = |
|
(const struct reftable_ref_record *)rec; |
|
return r->value_type; |
|
} |
|
|
|
static int reftable_ref_record_encode(const void *rec, struct string_view s, |
|
int hash_size) |
|
{ |
|
const struct reftable_ref_record *r = |
|
(const struct reftable_ref_record *)rec; |
|
struct string_view start = s; |
|
int n = put_var_int(&s, r->update_index); |
|
assert(hash_size > 0); |
|
if (n < 0) |
|
return -1; |
|
string_view_consume(&s, n); |
|
|
|
switch (r->value_type) { |
|
case REFTABLE_REF_SYMREF: |
|
n = encode_string(r->value.symref, s); |
|
if (n < 0) { |
|
return -1; |
|
} |
|
string_view_consume(&s, n); |
|
break; |
|
case REFTABLE_REF_VAL2: |
|
if (s.len < 2 * hash_size) { |
|
return -1; |
|
} |
|
memcpy(s.buf, r->value.val2.value, hash_size); |
|
string_view_consume(&s, hash_size); |
|
memcpy(s.buf, r->value.val2.target_value, hash_size); |
|
string_view_consume(&s, hash_size); |
|
break; |
|
case REFTABLE_REF_VAL1: |
|
if (s.len < hash_size) { |
|
return -1; |
|
} |
|
memcpy(s.buf, r->value.val1, hash_size); |
|
string_view_consume(&s, hash_size); |
|
break; |
|
case REFTABLE_REF_DELETION: |
|
break; |
|
default: |
|
abort(); |
|
} |
|
|
|
return start.len - s.len; |
|
} |
|
|
|
static int reftable_ref_record_decode(void *rec, struct strbuf key, |
|
uint8_t val_type, struct string_view in, |
|
int hash_size) |
|
{ |
|
struct reftable_ref_record *r = rec; |
|
struct string_view start = in; |
|
uint64_t update_index = 0; |
|
int n = get_var_int(&update_index, &in); |
|
if (n < 0) |
|
return n; |
|
string_view_consume(&in, n); |
|
|
|
reftable_ref_record_release(r); |
|
|
|
assert(hash_size > 0); |
|
|
|
r->refname = reftable_realloc(r->refname, key.len + 1); |
|
memcpy(r->refname, key.buf, key.len); |
|
r->update_index = update_index; |
|
r->refname[key.len] = 0; |
|
r->value_type = val_type; |
|
switch (val_type) { |
|
case REFTABLE_REF_VAL1: |
|
if (in.len < hash_size) { |
|
return -1; |
|
} |
|
|
|
r->value.val1 = reftable_malloc(hash_size); |
|
memcpy(r->value.val1, in.buf, hash_size); |
|
string_view_consume(&in, hash_size); |
|
break; |
|
|
|
case REFTABLE_REF_VAL2: |
|
if (in.len < 2 * hash_size) { |
|
return -1; |
|
} |
|
|
|
r->value.val2.value = reftable_malloc(hash_size); |
|
memcpy(r->value.val2.value, in.buf, hash_size); |
|
string_view_consume(&in, hash_size); |
|
|
|
r->value.val2.target_value = reftable_malloc(hash_size); |
|
memcpy(r->value.val2.target_value, in.buf, hash_size); |
|
string_view_consume(&in, hash_size); |
|
break; |
|
|
|
case REFTABLE_REF_SYMREF: { |
|
struct strbuf dest = STRBUF_INIT; |
|
int n = decode_string(&dest, in); |
|
if (n < 0) { |
|
return -1; |
|
} |
|
string_view_consume(&in, n); |
|
r->value.symref = dest.buf; |
|
} break; |
|
|
|
case REFTABLE_REF_DELETION: |
|
break; |
|
default: |
|
abort(); |
|
break; |
|
} |
|
|
|
return start.len - in.len; |
|
} |
|
|
|
static int reftable_ref_record_is_deletion_void(const void *p) |
|
{ |
|
return reftable_ref_record_is_deletion( |
|
(const struct reftable_ref_record *)p); |
|
} |
|
|
|
|
|
static int reftable_ref_record_equal_void(const void *a, |
|
const void *b, int hash_size) |
|
{ |
|
struct reftable_ref_record *ra = (struct reftable_ref_record *) a; |
|
struct reftable_ref_record *rb = (struct reftable_ref_record *) b; |
|
return reftable_ref_record_equal(ra, rb, hash_size); |
|
} |
|
|
|
static void reftable_ref_record_print_void(const void *rec, |
|
int hash_size) |
|
{ |
|
reftable_ref_record_print_sz((struct reftable_ref_record *) rec, hash_size); |
|
} |
|
|
|
static struct reftable_record_vtable reftable_ref_record_vtable = { |
|
.key = &reftable_ref_record_key, |
|
.type = BLOCK_TYPE_REF, |
|
.copy_from = &reftable_ref_record_copy_from, |
|
.val_type = &reftable_ref_record_val_type, |
|
.encode = &reftable_ref_record_encode, |
|
.decode = &reftable_ref_record_decode, |
|
.release = &reftable_ref_record_release_void, |
|
.is_deletion = &reftable_ref_record_is_deletion_void, |
|
.equal = &reftable_ref_record_equal_void, |
|
.print = &reftable_ref_record_print_void, |
|
}; |
|
|
|
static void reftable_obj_record_key(const void *r, struct strbuf *dest) |
|
{ |
|
const struct reftable_obj_record *rec = |
|
(const struct reftable_obj_record *)r; |
|
strbuf_reset(dest); |
|
strbuf_add(dest, rec->hash_prefix, rec->hash_prefix_len); |
|
} |
|
|
|
static void reftable_obj_record_release(void *rec) |
|
{ |
|
struct reftable_obj_record *obj = rec; |
|
FREE_AND_NULL(obj->hash_prefix); |
|
FREE_AND_NULL(obj->offsets); |
|
memset(obj, 0, sizeof(struct reftable_obj_record)); |
|
} |
|
|
|
static void reftable_obj_record_print(const void *rec, int hash_size) |
|
{ |
|
const struct reftable_obj_record *obj = rec; |
|
char hex[GIT_MAX_HEXSZ + 1] = { 0 }; |
|
struct strbuf offset_str = STRBUF_INIT; |
|
int i; |
|
|
|
for (i = 0; i < obj->offset_len; i++) |
|
strbuf_addf(&offset_str, "%" PRIu64 " ", obj->offsets[i]); |
|
hex_format(hex, obj->hash_prefix, obj->hash_prefix_len); |
|
printf("prefix %s (len %d), offsets [%s]\n", |
|
hex, obj->hash_prefix_len, offset_str.buf); |
|
strbuf_release(&offset_str); |
|
} |
|
|
|
static void reftable_obj_record_copy_from(void *rec, const void *src_rec, |
|
int hash_size) |
|
{ |
|
struct reftable_obj_record *obj = rec; |
|
const struct reftable_obj_record *src = |
|
(const struct reftable_obj_record *)src_rec; |
|
|
|
reftable_obj_record_release(obj); |
|
obj->hash_prefix = reftable_malloc(src->hash_prefix_len); |
|
obj->hash_prefix_len = src->hash_prefix_len; |
|
if (src->hash_prefix_len) |
|
memcpy(obj->hash_prefix, src->hash_prefix, obj->hash_prefix_len); |
|
|
|
obj->offsets = reftable_malloc(src->offset_len * sizeof(uint64_t)); |
|
obj->offset_len = src->offset_len; |
|
COPY_ARRAY(obj->offsets, src->offsets, src->offset_len); |
|
} |
|
|
|
static uint8_t reftable_obj_record_val_type(const void *rec) |
|
{ |
|
const struct reftable_obj_record *r = rec; |
|
if (r->offset_len > 0 && r->offset_len < 8) |
|
return r->offset_len; |
|
return 0; |
|
} |
|
|
|
static int reftable_obj_record_encode(const void *rec, struct string_view s, |
|
int hash_size) |
|
{ |
|
const struct reftable_obj_record *r = rec; |
|
struct string_view start = s; |
|
int i = 0; |
|
int n = 0; |
|
uint64_t last = 0; |
|
if (r->offset_len == 0 || r->offset_len >= 8) { |
|
n = put_var_int(&s, r->offset_len); |
|
if (n < 0) { |
|
return -1; |
|
} |
|
string_view_consume(&s, n); |
|
} |
|
if (r->offset_len == 0) |
|
return start.len - s.len; |
|
n = put_var_int(&s, r->offsets[0]); |
|
if (n < 0) |
|
return -1; |
|
string_view_consume(&s, n); |
|
|
|
last = r->offsets[0]; |
|
for (i = 1; i < r->offset_len; i++) { |
|
int n = put_var_int(&s, r->offsets[i] - last); |
|
if (n < 0) { |
|
return -1; |
|
} |
|
string_view_consume(&s, n); |
|
last = r->offsets[i]; |
|
} |
|
return start.len - s.len; |
|
} |
|
|
|
static int reftable_obj_record_decode(void *rec, struct strbuf key, |
|
uint8_t val_type, struct string_view in, |
|
int hash_size) |
|
{ |
|
struct string_view start = in; |
|
struct reftable_obj_record *r = rec; |
|
uint64_t count = val_type; |
|
int n = 0; |
|
uint64_t last; |
|
int j; |
|
r->hash_prefix = reftable_malloc(key.len); |
|
memcpy(r->hash_prefix, key.buf, key.len); |
|
r->hash_prefix_len = key.len; |
|
|
|
if (val_type == 0) { |
|
n = get_var_int(&count, &in); |
|
if (n < 0) { |
|
return n; |
|
} |
|
|
|
string_view_consume(&in, n); |
|
} |
|
|
|
r->offsets = NULL; |
|
r->offset_len = 0; |
|
if (count == 0) |
|
return start.len - in.len; |
|
|
|
r->offsets = reftable_malloc(count * sizeof(uint64_t)); |
|
r->offset_len = count; |
|
|
|
n = get_var_int(&r->offsets[0], &in); |
|
if (n < 0) |
|
return n; |
|
string_view_consume(&in, n); |
|
|
|
last = r->offsets[0]; |
|
j = 1; |
|
while (j < count) { |
|
uint64_t delta = 0; |
|
int n = get_var_int(&delta, &in); |
|
if (n < 0) { |
|
return n; |
|
} |
|
string_view_consume(&in, n); |
|
|
|
last = r->offsets[j] = (delta + last); |
|
j++; |
|
} |
|
return start.len - in.len; |
|
} |
|
|
|
static int not_a_deletion(const void *p) |
|
{ |
|
return 0; |
|
} |
|
|
|
static int reftable_obj_record_equal_void(const void *a, const void *b, int hash_size) |
|
{ |
|
struct reftable_obj_record *ra = (struct reftable_obj_record *) a; |
|
struct reftable_obj_record *rb = (struct reftable_obj_record *) b; |
|
|
|
if (ra->hash_prefix_len != rb->hash_prefix_len |
|
|| ra->offset_len != rb->offset_len) |
|
return 0; |
|
|
|
if (ra->hash_prefix_len && |
|
memcmp(ra->hash_prefix, rb->hash_prefix, ra->hash_prefix_len)) |
|
return 0; |
|
if (ra->offset_len && |
|
memcmp(ra->offsets, rb->offsets, ra->offset_len * sizeof(uint64_t))) |
|
return 0; |
|
|
|
return 1; |
|
} |
|
|
|
static struct reftable_record_vtable reftable_obj_record_vtable = { |
|
.key = &reftable_obj_record_key, |
|
.type = BLOCK_TYPE_OBJ, |
|
.copy_from = &reftable_obj_record_copy_from, |
|
.val_type = &reftable_obj_record_val_type, |
|
.encode = &reftable_obj_record_encode, |
|
.decode = &reftable_obj_record_decode, |
|
.release = &reftable_obj_record_release, |
|
.is_deletion = ¬_a_deletion, |
|
.equal = &reftable_obj_record_equal_void, |
|
.print = &reftable_obj_record_print, |
|
}; |
|
|
|
static void reftable_log_record_print_sz(struct reftable_log_record *log, |
|
int hash_size) |
|
{ |
|
char hex[GIT_MAX_HEXSZ + 1] = { 0 }; |
|
|
|
switch (log->value_type) { |
|
case REFTABLE_LOG_DELETION: |
|
printf("log{%s(%" PRIu64 ") delete\n", log->refname, |
|
log->update_index); |
|
break; |
|
case REFTABLE_LOG_UPDATE: |
|
printf("log{%s(%" PRIu64 ") %s <%s> %" PRIu64 " %04d\n", |
|
log->refname, log->update_index, |
|
log->value.update.name ? log->value.update.name : "", |
|
log->value.update.email ? log->value.update.email : "", |
|
log->value.update.time, |
|
log->value.update.tz_offset); |
|
hex_format(hex, log->value.update.old_hash, hash_size); |
|
printf("%s => ", hex); |
|
hex_format(hex, log->value.update.new_hash, hash_size); |
|
printf("%s\n\n%s\n}\n", hex, |
|
log->value.update.message ? log->value.update.message : ""); |
|
break; |
|
} |
|
} |
|
|
|
void reftable_log_record_print(struct reftable_log_record *log, |
|
uint32_t hash_id) |
|
{ |
|
reftable_log_record_print_sz(log, hash_size(hash_id)); |
|
} |
|
|
|
static void reftable_log_record_key(const void *r, struct strbuf *dest) |
|
{ |
|
const struct reftable_log_record *rec = |
|
(const struct reftable_log_record *)r; |
|
int len = strlen(rec->refname); |
|
uint8_t i64[8]; |
|
uint64_t ts = 0; |
|
strbuf_reset(dest); |
|
strbuf_add(dest, (uint8_t *)rec->refname, len + 1); |
|
|
|
ts = (~ts) - rec->update_index; |
|
put_be64(&i64[0], ts); |
|
strbuf_add(dest, i64, sizeof(i64)); |
|
} |
|
|
|
static void reftable_log_record_copy_from(void *rec, const void *src_rec, |
|
int hash_size) |
|
{ |
|
struct reftable_log_record *dst = rec; |
|
const struct reftable_log_record *src = |
|
(const struct reftable_log_record *)src_rec; |
|
|
|
reftable_log_record_release(dst); |
|
*dst = *src; |
|
if (dst->refname) { |
|
dst->refname = xstrdup(dst->refname); |
|
} |
|
switch (dst->value_type) { |
|
case REFTABLE_LOG_DELETION: |
|
break; |
|
case REFTABLE_LOG_UPDATE: |
|
if (dst->value.update.email) { |
|
dst->value.update.email = |
|
xstrdup(dst->value.update.email); |
|
} |
|
if (dst->value.update.name) { |
|
dst->value.update.name = |
|
xstrdup(dst->value.update.name); |
|
} |
|
if (dst->value.update.message) { |
|
dst->value.update.message = |
|
xstrdup(dst->value.update.message); |
|
} |
|
|
|
if (dst->value.update.new_hash) { |
|
dst->value.update.new_hash = reftable_malloc(hash_size); |
|
memcpy(dst->value.update.new_hash, |
|
src->value.update.new_hash, hash_size); |
|
} |
|
if (dst->value.update.old_hash) { |
|
dst->value.update.old_hash = reftable_malloc(hash_size); |
|
memcpy(dst->value.update.old_hash, |
|
src->value.update.old_hash, hash_size); |
|
} |
|
break; |
|
} |
|
} |
|
|
|
static void reftable_log_record_release_void(void *rec) |
|
{ |
|
struct reftable_log_record *r = rec; |
|
reftable_log_record_release(r); |
|
} |
|
|
|
void reftable_log_record_release(struct reftable_log_record *r) |
|
{ |
|
reftable_free(r->refname); |
|
switch (r->value_type) { |
|
case REFTABLE_LOG_DELETION: |
|
break; |
|
case REFTABLE_LOG_UPDATE: |
|
reftable_free(r->value.update.new_hash); |
|
reftable_free(r->value.update.old_hash); |
|
reftable_free(r->value.update.name); |
|
reftable_free(r->value.update.email); |
|
reftable_free(r->value.update.message); |
|
break; |
|
} |
|
memset(r, 0, sizeof(struct reftable_log_record)); |
|
} |
|
|
|
static uint8_t reftable_log_record_val_type(const void *rec) |
|
{ |
|
const struct reftable_log_record *log = |
|
(const struct reftable_log_record *)rec; |
|
|
|
return reftable_log_record_is_deletion(log) ? 0 : 1; |
|
} |
|
|
|
static uint8_t zero[GIT_SHA256_RAWSZ] = { 0 }; |
|
|
|
static int reftable_log_record_encode(const void *rec, struct string_view s, |
|
int hash_size) |
|
{ |
|
const struct reftable_log_record *r = rec; |
|
struct string_view start = s; |
|
int n = 0; |
|
uint8_t *oldh = NULL; |
|
uint8_t *newh = NULL; |
|
if (reftable_log_record_is_deletion(r)) |
|
return 0; |
|
|
|
oldh = r->value.update.old_hash; |
|
newh = r->value.update.new_hash; |
|
if (!oldh) { |
|
oldh = zero; |
|
} |
|
if (!newh) { |
|
newh = zero; |
|
} |
|
|
|
if (s.len < 2 * hash_size) |
|
return -1; |
|
|
|
memcpy(s.buf, oldh, hash_size); |
|
memcpy(s.buf + hash_size, newh, hash_size); |
|
string_view_consume(&s, 2 * hash_size); |
|
|
|
n = encode_string(r->value.update.name ? r->value.update.name : "", s); |
|
if (n < 0) |
|
return -1; |
|
string_view_consume(&s, n); |
|
|
|
n = encode_string(r->value.update.email ? r->value.update.email : "", |
|
s); |
|
if (n < 0) |
|
return -1; |
|
string_view_consume(&s, n); |
|
|
|
n = put_var_int(&s, r->value.update.time); |
|
if (n < 0) |
|
return -1; |
|
string_view_consume(&s, n); |
|
|
|
if (s.len < 2) |
|
return -1; |
|
|
|
put_be16(s.buf, r->value.update.tz_offset); |
|
string_view_consume(&s, 2); |
|
|
|
n = encode_string( |
|
r->value.update.message ? r->value.update.message : "", s); |
|
if (n < 0) |
|
return -1; |
|
string_view_consume(&s, n); |
|
|
|
return start.len - s.len; |
|
} |
|
|
|
static int reftable_log_record_decode(void *rec, struct strbuf key, |
|
uint8_t val_type, struct string_view in, |
|
int hash_size) |
|
{ |
|
struct string_view start = in; |
|
struct reftable_log_record *r = rec; |
|
uint64_t max = 0; |
|
uint64_t ts = 0; |
|
struct strbuf dest = STRBUF_INIT; |
|
int n; |
|
|
|
if (key.len <= 9 || key.buf[key.len - 9] != 0) |
|
return REFTABLE_FORMAT_ERROR; |
|
|
|
r->refname = reftable_realloc(r->refname, key.len - 8); |
|
memcpy(r->refname, key.buf, key.len - 8); |
|
ts = get_be64(key.buf + key.len - 8); |
|
|
|
r->update_index = (~max) - ts; |
|
|
|
if (val_type != r->value_type) { |
|
switch (r->value_type) { |
|
case REFTABLE_LOG_UPDATE: |
|
FREE_AND_NULL(r->value.update.old_hash); |
|
FREE_AND_NULL(r->value.update.new_hash); |
|
FREE_AND_NULL(r->value.update.message); |
|
FREE_AND_NULL(r->value.update.email); |
|
FREE_AND_NULL(r->value.update.name); |
|
break; |
|
case REFTABLE_LOG_DELETION: |
|
break; |
|
} |
|
} |
|
|
|
r->value_type = val_type; |
|
if (val_type == REFTABLE_LOG_DELETION) |
|
return 0; |
|
|
|
if (in.len < 2 * hash_size) |
|
return REFTABLE_FORMAT_ERROR; |
|
|
|
r->value.update.old_hash = |
|
reftable_realloc(r->value.update.old_hash, hash_size); |
|
r->value.update.new_hash = |
|
reftable_realloc(r->value.update.new_hash, hash_size); |
|
|
|
memcpy(r->value.update.old_hash, in.buf, hash_size); |
|
memcpy(r->value.update.new_hash, in.buf + hash_size, hash_size); |
|
|
|
string_view_consume(&in, 2 * hash_size); |
|
|
|
n = decode_string(&dest, in); |
|
if (n < 0) |
|
goto done; |
|
string_view_consume(&in, n); |
|
|
|
r->value.update.name = |
|
reftable_realloc(r->value.update.name, dest.len + 1); |
|
memcpy(r->value.update.name, dest.buf, dest.len); |
|
r->value.update.name[dest.len] = 0; |
|
|
|
strbuf_reset(&dest); |
|
n = decode_string(&dest, in); |
|
if (n < 0) |
|
goto done; |
|
string_view_consume(&in, n); |
|
|
|
r->value.update.email = |
|
reftable_realloc(r->value.update.email, dest.len + 1); |
|
memcpy(r->value.update.email, dest.buf, dest.len); |
|
r->value.update.email[dest.len] = 0; |
|
|
|
ts = 0; |
|
n = get_var_int(&ts, &in); |
|
if (n < 0) |
|
goto done; |
|
string_view_consume(&in, n); |
|
r->value.update.time = ts; |
|
if (in.len < 2) |
|
goto done; |
|
|
|
r->value.update.tz_offset = get_be16(in.buf); |
|
string_view_consume(&in, 2); |
|
|
|
strbuf_reset(&dest); |
|
n = decode_string(&dest, in); |
|
if (n < 0) |
|
goto done; |
|
string_view_consume(&in, n); |
|
|
|
r->value.update.message = |
|
reftable_realloc(r->value.update.message, dest.len + 1); |
|
memcpy(r->value.update.message, dest.buf, dest.len); |
|
r->value.update.message[dest.len] = 0; |
|
|
|
strbuf_release(&dest); |
|
return start.len - in.len; |
|
|
|
done: |
|
strbuf_release(&dest); |
|
return REFTABLE_FORMAT_ERROR; |
|
} |
|
|
|
static int null_streq(char *a, char *b) |
|
{ |
|
char *empty = ""; |
|
if (!a) |
|
a = empty; |
|
|
|
if (!b) |
|
b = empty; |
|
|
|
return 0 == strcmp(a, b); |
|
} |
|
|
|
static int zero_hash_eq(uint8_t *a, uint8_t *b, int sz) |
|
{ |
|
if (!a) |
|
a = zero; |
|
|
|
if (!b) |
|
b = zero; |
|
|
|
return !memcmp(a, b, sz); |
|
} |
|
|
|
static int reftable_log_record_equal_void(const void *a, |
|
const void *b, int hash_size) |
|
{ |
|
return reftable_log_record_equal((struct reftable_log_record *) a, |
|
(struct reftable_log_record *) b, |
|
hash_size); |
|
} |
|
|
|
int reftable_log_record_equal(const struct reftable_log_record *a, |
|
const struct reftable_log_record *b, int hash_size) |
|
{ |
|
if (!(null_streq(a->refname, b->refname) && |
|
a->update_index == b->update_index && |
|
a->value_type == b->value_type)) |
|
return 0; |
|
|
|
switch (a->value_type) { |
|
case REFTABLE_LOG_DELETION: |
|
return 1; |
|
case REFTABLE_LOG_UPDATE: |
|
return null_streq(a->value.update.name, b->value.update.name) && |
|
a->value.update.time == b->value.update.time && |
|
a->value.update.tz_offset == b->value.update.tz_offset && |
|
null_streq(a->value.update.email, |
|
b->value.update.email) && |
|
null_streq(a->value.update.message, |
|
b->value.update.message) && |
|
zero_hash_eq(a->value.update.old_hash, |
|
b->value.update.old_hash, hash_size) && |
|
zero_hash_eq(a->value.update.new_hash, |
|
b->value.update.new_hash, hash_size); |
|
} |
|
|
|
abort(); |
|
} |
|
|
|
static int reftable_log_record_is_deletion_void(const void *p) |
|
{ |
|
return reftable_log_record_is_deletion( |
|
(const struct reftable_log_record *)p); |
|
} |
|
|
|
static void reftable_log_record_print_void(const void *rec, int hash_size) |
|
{ |
|
reftable_log_record_print_sz((struct reftable_log_record*)rec, hash_size); |
|
} |
|
|
|
static struct reftable_record_vtable reftable_log_record_vtable = { |
|
.key = &reftable_log_record_key, |
|
.type = BLOCK_TYPE_LOG, |
|
.copy_from = &reftable_log_record_copy_from, |
|
.val_type = &reftable_log_record_val_type, |
|
.encode = &reftable_log_record_encode, |
|
.decode = &reftable_log_record_decode, |
|
.release = &reftable_log_record_release_void, |
|
.is_deletion = &reftable_log_record_is_deletion_void, |
|
.equal = &reftable_log_record_equal_void, |
|
.print = &reftable_log_record_print_void, |
|
}; |
|
|
|
static void reftable_index_record_key(const void *r, struct strbuf *dest) |
|
{ |
|
const struct reftable_index_record *rec = r; |
|
strbuf_reset(dest); |
|
strbuf_addbuf(dest, &rec->last_key); |
|
} |
|
|
|
static void reftable_index_record_copy_from(void *rec, const void *src_rec, |
|
int hash_size) |
|
{ |
|
struct reftable_index_record *dst = rec; |
|
const struct reftable_index_record *src = src_rec; |
|
|
|
strbuf_reset(&dst->last_key); |
|
strbuf_addbuf(&dst->last_key, &src->last_key); |
|
dst->offset = src->offset; |
|
} |
|
|
|
static void reftable_index_record_release(void *rec) |
|
{ |
|
struct reftable_index_record *idx = rec; |
|
strbuf_release(&idx->last_key); |
|
} |
|
|
|
static uint8_t reftable_index_record_val_type(const void *rec) |
|
{ |
|
return 0; |
|
} |
|
|
|
static int reftable_index_record_encode(const void *rec, struct string_view out, |
|
int hash_size) |
|
{ |
|
const struct reftable_index_record *r = |
|
(const struct reftable_index_record *)rec; |
|
struct string_view start = out; |
|
|
|
int n = put_var_int(&out, r->offset); |
|
if (n < 0) |
|
return n; |
|
|
|
string_view_consume(&out, n); |
|
|
|
return start.len - out.len; |
|
} |
|
|
|
static int reftable_index_record_decode(void *rec, struct strbuf key, |
|
uint8_t val_type, struct string_view in, |
|
int hash_size) |
|
{ |
|
struct string_view start = in; |
|
struct reftable_index_record *r = rec; |
|
int n = 0; |
|
|
|
strbuf_reset(&r->last_key); |
|
strbuf_addbuf(&r->last_key, &key); |
|
|
|
n = get_var_int(&r->offset, &in); |
|
if (n < 0) |
|
return n; |
|
|
|
string_view_consume(&in, n); |
|
return start.len - in.len; |
|
} |
|
|
|
static int reftable_index_record_equal(const void *a, const void *b, int hash_size) |
|
{ |
|
struct reftable_index_record *ia = (struct reftable_index_record *) a; |
|
struct reftable_index_record *ib = (struct reftable_index_record *) b; |
|
|
|
return ia->offset == ib->offset && !strbuf_cmp(&ia->last_key, &ib->last_key); |
|
} |
|
|
|
static void reftable_index_record_print(const void *rec, int hash_size) |
|
{ |
|
const struct reftable_index_record *idx = rec; |
|
/* TODO: escape null chars? */ |
|
printf("\"%s\" %" PRIu64 "\n", idx->last_key.buf, idx->offset); |
|
} |
|
|
|
static struct reftable_record_vtable reftable_index_record_vtable = { |
|
.key = &reftable_index_record_key, |
|
.type = BLOCK_TYPE_INDEX, |
|
.copy_from = &reftable_index_record_copy_from, |
|
.val_type = &reftable_index_record_val_type, |
|
.encode = &reftable_index_record_encode, |
|
.decode = &reftable_index_record_decode, |
|
.release = &reftable_index_record_release, |
|
.is_deletion = ¬_a_deletion, |
|
.equal = &reftable_index_record_equal, |
|
.print = &reftable_index_record_print, |
|
}; |
|
|
|
void reftable_record_key(struct reftable_record *rec, struct strbuf *dest) |
|
{ |
|
reftable_record_vtable(rec)->key(reftable_record_data(rec), dest); |
|
} |
|
|
|
uint8_t reftable_record_type(struct reftable_record *rec) |
|
{ |
|
return rec->type; |
|
} |
|
|
|
int reftable_record_encode(struct reftable_record *rec, struct string_view dest, |
|
int hash_size) |
|
{ |
|
return reftable_record_vtable(rec)->encode(reftable_record_data(rec), |
|
dest, hash_size); |
|
} |
|
|
|
void reftable_record_copy_from(struct reftable_record *rec, |
|
struct reftable_record *src, int hash_size) |
|
{ |
|
assert(src->type == rec->type); |
|
|
|
reftable_record_vtable(rec)->copy_from(reftable_record_data(rec), |
|
reftable_record_data(src), |
|
hash_size); |
|
} |
|
|
|
uint8_t reftable_record_val_type(struct reftable_record *rec) |
|
{ |
|
return reftable_record_vtable(rec)->val_type(reftable_record_data(rec)); |
|
} |
|
|
|
int reftable_record_decode(struct reftable_record *rec, struct strbuf key, |
|
uint8_t extra, struct string_view src, int hash_size) |
|
{ |
|
return reftable_record_vtable(rec)->decode(reftable_record_data(rec), |
|
key, extra, src, hash_size); |
|
} |
|
|
|
void reftable_record_release(struct reftable_record *rec) |
|
{ |
|
reftable_record_vtable(rec)->release(reftable_record_data(rec)); |
|
} |
|
|
|
int reftable_record_is_deletion(struct reftable_record *rec) |
|
{ |
|
return reftable_record_vtable(rec)->is_deletion( |
|
reftable_record_data(rec)); |
|
} |
|
|
|
int reftable_record_equal(struct reftable_record *a, struct reftable_record *b, int hash_size) |
|
{ |
|
if (a->type != b->type) |
|
return 0; |
|
return reftable_record_vtable(a)->equal( |
|
reftable_record_data(a), reftable_record_data(b), hash_size); |
|
} |
|
|
|
static int hash_equal(uint8_t *a, uint8_t *b, int hash_size) |
|
{ |
|
if (a && b) |
|
return !memcmp(a, b, hash_size); |
|
|
|
return a == b; |
|
} |
|
|
|
int reftable_ref_record_equal(const struct reftable_ref_record *a, |
|
const struct reftable_ref_record *b, int hash_size) |
|
{ |
|
assert(hash_size > 0); |
|
if (!null_streq(a->refname, b->refname)) |
|
return 0; |
|
|
|
if (a->update_index != b->update_index || |
|
a->value_type != b->value_type) |
|
return 0; |
|
|
|
switch (a->value_type) { |
|
case REFTABLE_REF_SYMREF: |
|
return !strcmp(a->value.symref, b->value.symref); |
|
case REFTABLE_REF_VAL2: |
|
return hash_equal(a->value.val2.value, b->value.val2.value, |
|
hash_size) && |
|
hash_equal(a->value.val2.target_value, |
|
b->value.val2.target_value, hash_size); |
|
case REFTABLE_REF_VAL1: |
|
return hash_equal(a->value.val1, b->value.val1, hash_size); |
|
case REFTABLE_REF_DELETION: |
|
return 1; |
|
default: |
|
abort(); |
|
} |
|
} |
|
|
|
int reftable_ref_record_compare_name(const void *a, const void *b) |
|
{ |
|
return strcmp(((struct reftable_ref_record *)a)->refname, |
|
((struct reftable_ref_record *)b)->refname); |
|
} |
|
|
|
int reftable_ref_record_is_deletion(const struct reftable_ref_record *ref) |
|
{ |
|
return ref->value_type == REFTABLE_REF_DELETION; |
|
} |
|
|
|
int reftable_log_record_compare_key(const void *a, const void *b) |
|
{ |
|
const struct reftable_log_record *la = a; |
|
const struct reftable_log_record *lb = b; |
|
|
|
int cmp = strcmp(la->refname, lb->refname); |
|
if (cmp) |
|
return cmp; |
|
if (la->update_index > lb->update_index) |
|
return -1; |
|
return (la->update_index < lb->update_index) ? 1 : 0; |
|
} |
|
|
|
int reftable_log_record_is_deletion(const struct reftable_log_record *log) |
|
{ |
|
return (log->value_type == REFTABLE_LOG_DELETION); |
|
} |
|
|
|
void string_view_consume(struct string_view *s, int n) |
|
{ |
|
s->buf += n; |
|
s->len -= n; |
|
} |
|
|
|
static void *reftable_record_data(struct reftable_record *rec) |
|
{ |
|
switch (rec->type) { |
|
case BLOCK_TYPE_REF: |
|
return &rec->u.ref; |
|
case BLOCK_TYPE_LOG: |
|
return &rec->u.log; |
|
case BLOCK_TYPE_INDEX: |
|
return &rec->u.idx; |
|
case BLOCK_TYPE_OBJ: |
|
return &rec->u.obj; |
|
} |
|
abort(); |
|
} |
|
|
|
static struct reftable_record_vtable * |
|
reftable_record_vtable(struct reftable_record *rec) |
|
{ |
|
switch (rec->type) { |
|
case BLOCK_TYPE_REF: |
|
return &reftable_ref_record_vtable; |
|
case BLOCK_TYPE_LOG: |
|
return &reftable_log_record_vtable; |
|
case BLOCK_TYPE_INDEX: |
|
return &reftable_index_record_vtable; |
|
case BLOCK_TYPE_OBJ: |
|
return &reftable_obj_record_vtable; |
|
} |
|
abort(); |
|
} |
|
|
|
struct reftable_record reftable_new_record(uint8_t typ) |
|
{ |
|
struct reftable_record clean = { |
|
.type = typ, |
|
}; |
|
|
|
/* the following is involved, but the naive solution (just return |
|
* `clean` as is, except for BLOCK_TYPE_INDEX), returns a garbage |
|
* clean.u.obj.offsets pointer on Windows VS CI. Go figure. |
|
*/ |
|
switch (typ) { |
|
case BLOCK_TYPE_OBJ: |
|
{ |
|
struct reftable_obj_record obj = { 0 }; |
|
clean.u.obj = obj; |
|
break; |
|
} |
|
case BLOCK_TYPE_INDEX: |
|
{ |
|
struct reftable_index_record idx = { |
|
.last_key = STRBUF_INIT, |
|
}; |
|
clean.u.idx = idx; |
|
break; |
|
} |
|
case BLOCK_TYPE_REF: |
|
{ |
|
struct reftable_ref_record ref = { 0 }; |
|
clean.u.ref = ref; |
|
break; |
|
} |
|
case BLOCK_TYPE_LOG: |
|
{ |
|
struct reftable_log_record log = { 0 }; |
|
clean.u.log = log; |
|
break; |
|
} |
|
} |
|
return clean; |
|
} |
|
|
|
void reftable_record_print(struct reftable_record *rec, int hash_size) |
|
{ |
|
printf("'%c': ", rec->type); |
|
reftable_record_vtable(rec)->print(reftable_record_data(rec), hash_size); |
|
}
|
|
|