| /* |
| 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; |
| } |
| |
| const unsigned char *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; |
| } |
| } |
| |
| const unsigned char *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: |
| memcpy(ref->value.val1, src->value.val1, hash_size); |
| break; |
| case REFTABLE_REF_VAL2: |
| memcpy(ref->value.val2.value, src->value.val2.value, 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, const unsigned char *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: |
| break; |
| case REFTABLE_REF_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; |
| } |
| |
| 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; |
| } |
| |
| memcpy(r->value.val2.value, in.buf, hash_size); |
| string_view_consume(&in, 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); |
| |
| REFTABLE_ALLOC_ARRAY(obj->hash_prefix, 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); |
| |
| REFTABLE_ALLOC_ARRAY(obj->offsets, src->offset_len); |
| 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; |
| |
| REFTABLE_ALLOC_ARRAY(r->hash_prefix, 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; |
| |
| REFTABLE_ALLOC_ARRAY(r->offsets, count); |
| 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) { |
| REFTABLE_ALLOC_ARRAY(dst->value.update.new_hash, hash_size); |
| memcpy(dst->value.update.new_hash, |
| src->value.update.new_hash, hash_size); |
| } |
| if (dst->value.update.old_hash) { |
| REFTABLE_ALLOC_ARRAY(dst->value.update.old_hash, 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(const unsigned char *a, const unsigned char *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(); |
| } |
| |
| void reftable_record_init(struct reftable_record *rec, uint8_t typ) |
| { |
| memset(rec, 0, sizeof(*rec)); |
| rec->type = typ; |
| |
| switch (typ) { |
| case BLOCK_TYPE_REF: |
| case BLOCK_TYPE_LOG: |
| case BLOCK_TYPE_OBJ: |
| return; |
| case BLOCK_TYPE_INDEX: |
| strbuf_init(&rec->u.idx.last_key, 0); |
| return; |
| default: |
| BUG("unhandled record type"); |
| } |
| } |
| |
| 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); |
| } |