summaryrefslogtreecommitdiff
path: root/reftable/record.c
diff options
context:
space:
mode:
Diffstat (limited to 'reftable/record.c')
-rw-r--r--reftable/record.c1322
1 files changed, 1322 insertions, 0 deletions
diff --git a/reftable/record.c b/reftable/record.c
new file mode 100644
index 0000000000..fcd387ba5d
--- /dev/null
+++ b/reftable/record.c
@@ -0,0 +1,1322 @@
+/*
+ * 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)
+{
+ const unsigned char *buf = in->buf;
+ unsigned char c;
+ uint64_t val;
+
+ if (!in->len)
+ return -1;
+ c = *buf++;
+ val = c & 0x7f;
+
+ while (c & 0x80) {
+ /*
+ * We use a micro-optimization here: whenever we see that the
+ * 0x80 bit is set, we know that the remainder of the value
+ * cannot be 0. The zero-values thus doesn't need to be encoded
+ * at all, which is why we subtract 1 when encoding and add 1
+ * when decoding.
+ *
+ * This allows us to save a byte in some edge cases.
+ */
+ val += 1;
+ if (!val || (val & (uint64_t)(~0ULL << (64 - 7))))
+ return -1; /* overflow */
+ if (buf >= in->buf + in->len)
+ return -1;
+ c = *buf++;
+ val = (val << 7) + (c & 0x7f);
+ }
+
+ *dest = val;
+ return buf - in->buf;
+}
+
+int put_var_int(struct string_view *dest, uint64_t value)
+{
+ unsigned char varint[10];
+ unsigned pos = sizeof(varint) - 1;
+ varint[pos] = value & 0x7f;
+ while (value >>= 7)
+ varint[--pos] = 0x80 | (--value & 0x7f);
+ if (dest->len < sizeof(varint) - pos)
+ return REFTABLE_ENTRY_TOO_BIG_ERROR;
+ memcpy(dest->buf, varint + pos, sizeof(varint) - pos);
+ return sizeof(varint) - pos;
+}
+
+int reftable_is_block_type(uint8_t typ)
+{
+ switch (typ) {
+ case REFTABLE_BLOCK_TYPE_REF:
+ case REFTABLE_BLOCK_TYPE_LOG:
+ case REFTABLE_BLOCK_TYPE_OBJ:
+ case REFTABLE_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 reftable_buf *dest, struct string_view in)
+{
+ int start_len = in.len;
+ uint64_t tsize = 0;
+ int n, err;
+
+ n = get_var_int(&tsize, &in);
+ if (n <= 0)
+ return -1;
+ string_view_consume(&in, n);
+ if (in.len < tsize)
+ return -1;
+
+ reftable_buf_reset(dest);
+ err = reftable_buf_add(dest, in.buf, tsize);
+ if (err < 0)
+ return err;
+
+ string_view_consume(&in, tsize);
+
+ return start_len - in.len;
+}
+
+static int encode_string(const char *str, struct string_view s)
+{
+ struct string_view start = s;
+ size_t l = strlen(str);
+ int n = put_var_int(&s, l);
+ if (n < 0)
+ return n;
+ string_view_consume(&s, n);
+ if (s.len < l)
+ return REFTABLE_ENTRY_TOO_BIG_ERROR;
+ 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 reftable_buf prev_key, struct reftable_buf key,
+ uint8_t extra)
+{
+ struct string_view start = dest;
+ size_t prefix_len = common_prefix_size(&prev_key, &key);
+ uint64_t suffix_len = key.len - prefix_len;
+ int n = put_var_int(&dest, prefix_len);
+ if (n < 0)
+ return n;
+ string_view_consume(&dest, n);
+
+ *restart = (prefix_len == 0);
+
+ n = put_var_int(&dest, suffix_len << 3 | (uint64_t)extra);
+ if (n < 0)
+ return n;
+ string_view_consume(&dest, n);
+
+ if (dest.len < suffix_len)
+ return REFTABLE_ENTRY_TOO_BIG_ERROR;
+ memcpy(dest.buf, key.buf + prefix_len, suffix_len);
+ string_view_consume(&dest, suffix_len);
+
+ return start.len - dest.len;
+}
+
+int reftable_decode_keylen(struct string_view in,
+ uint64_t *prefix_len,
+ uint64_t *suffix_len,
+ uint8_t *extra)
+{
+ size_t start_len = in.len;
+ int n;
+
+ n = get_var_int(prefix_len, &in);
+ if (n < 0)
+ return -1;
+ string_view_consume(&in, n);
+
+ 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;
+
+ return start_len - in.len;
+}
+
+int reftable_decode_key(struct reftable_buf *last_key, uint8_t *extra,
+ struct string_view in)
+{
+ int start_len = in.len;
+ uint64_t prefix_len = 0;
+ uint64_t suffix_len = 0;
+ int err, n;
+
+ n = reftable_decode_keylen(in, &prefix_len, &suffix_len, extra);
+ if (n < 0)
+ return -1;
+ string_view_consume(&in, n);
+
+ if (in.len < suffix_len ||
+ prefix_len > last_key->len)
+ return -1;
+
+ err = reftable_buf_setlen(last_key, prefix_len);
+ if (err < 0)
+ return err;
+
+ err = reftable_buf_add(last_key, in.buf, suffix_len);
+ if (err < 0)
+ return err;
+
+ string_view_consume(&in, suffix_len);
+
+ return start_len - in.len;
+}
+
+static int reftable_ref_record_key(const void *r, struct reftable_buf *dest)
+{
+ const struct reftable_ref_record *rec =
+ (const struct reftable_ref_record *)r;
+ reftable_buf_reset(dest);
+ return reftable_buf_addstr(dest, rec->refname);
+}
+
+static int reftable_ref_record_copy_from(void *rec, const void *src_rec,
+ uint32_t hash_size)
+{
+ struct reftable_ref_record *ref = rec;
+ const struct reftable_ref_record *src = src_rec;
+ char *refname = NULL;
+ size_t refname_cap = 0;
+ int err;
+
+ REFTABLE_SWAP(refname, ref->refname);
+ REFTABLE_SWAP(refname_cap, ref->refname_cap);
+ reftable_ref_record_release(ref);
+ REFTABLE_SWAP(ref->refname, refname);
+ REFTABLE_SWAP(ref->refname_cap, refname_cap);
+
+ if (src->refname) {
+ size_t refname_len = strlen(src->refname);
+
+ REFTABLE_ALLOC_GROW_OR_NULL(ref->refname, refname_len + 1,
+ ref->refname_cap);
+ if (!ref->refname) {
+ err = REFTABLE_OUT_OF_MEMORY_ERROR;
+ goto out;
+ }
+
+ memcpy(ref->refname, src->refname, refname_len);
+ ref->refname[refname_len] = 0;
+ }
+
+ 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 = reftable_strdup(src->value.symref);
+ if (!ref->value.symref) {
+ err = REFTABLE_OUT_OF_MEMORY_ERROR;
+ goto out;
+ }
+ break;
+ }
+
+ err = 0;
+out:
+ return err;
+}
+
+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,
+ uint32_t 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);
+ if (n < 0)
+ return n;
+ string_view_consume(&s, n);
+
+ switch (r->value_type) {
+ case REFTABLE_REF_SYMREF:
+ n = encode_string(r->value.symref, s);
+ if (n < 0)
+ return n;
+ string_view_consume(&s, n);
+ break;
+ case REFTABLE_REF_VAL2:
+ if (s.len < 2 * hash_size)
+ return REFTABLE_ENTRY_TOO_BIG_ERROR;
+ 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 REFTABLE_ENTRY_TOO_BIG_ERROR;
+ 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 reftable_buf key,
+ uint8_t val_type, struct string_view in,
+ uint32_t hash_size, struct reftable_buf *scratch)
+{
+ struct reftable_ref_record *r = rec;
+ struct string_view start = in;
+ uint64_t update_index = 0;
+ const char *refname = NULL;
+ size_t refname_cap = 0;
+ int n, err;
+
+ n = get_var_int(&update_index, &in);
+ if (n < 0)
+ return n;
+ string_view_consume(&in, n);
+
+ REFTABLE_SWAP(refname, r->refname);
+ REFTABLE_SWAP(refname_cap, r->refname_cap);
+ reftable_ref_record_release(r);
+ REFTABLE_SWAP(r->refname, refname);
+ REFTABLE_SWAP(r->refname_cap, refname_cap);
+
+ REFTABLE_ALLOC_GROW_OR_NULL(r->refname, key.len + 1, r->refname_cap);
+ if (!r->refname) {
+ err = REFTABLE_OUT_OF_MEMORY_ERROR;
+ goto done;
+ }
+ memcpy(r->refname, key.buf, key.len);
+ r->refname[key.len] = 0;
+
+ r->update_index = update_index;
+ r->value_type = val_type;
+ switch (val_type) {
+ case REFTABLE_REF_VAL1:
+ if (in.len < hash_size) {
+ err = REFTABLE_FORMAT_ERROR;
+ goto done;
+ }
+
+ 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) {
+ err = REFTABLE_FORMAT_ERROR;
+ goto done;
+ }
+
+ 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: {
+ int n = decode_string(scratch, in);
+ if (n < 0) {
+ err = REFTABLE_FORMAT_ERROR;
+ goto done;
+ }
+ string_view_consume(&in, n);
+ r->value.symref = reftable_buf_detach(scratch);
+ } break;
+
+ case REFTABLE_REF_DELETION:
+ break;
+ default:
+ abort();
+ break;
+ }
+
+ return start.len - in.len;
+
+done:
+ return err;
+}
+
+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, uint32_t 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 int reftable_ref_record_cmp_void(const void *_a, const void *_b)
+{
+ const struct reftable_ref_record *a = _a;
+ const struct reftable_ref_record *b = _b;
+ return strcmp(a->refname, b->refname);
+}
+
+static struct reftable_record_vtable reftable_ref_record_vtable = {
+ .key = &reftable_ref_record_key,
+ .type = REFTABLE_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,
+ .cmp = &reftable_ref_record_cmp_void,
+};
+
+static int reftable_obj_record_key(const void *r, struct reftable_buf *dest)
+{
+ const struct reftable_obj_record *rec =
+ (const struct reftable_obj_record *)r;
+ reftable_buf_reset(dest);
+ return reftable_buf_add(dest, rec->hash_prefix, rec->hash_prefix_len);
+}
+
+static void reftable_obj_record_release(void *rec)
+{
+ struct reftable_obj_record *obj = rec;
+ REFTABLE_FREE_AND_NULL(obj->hash_prefix);
+ REFTABLE_FREE_AND_NULL(obj->offsets);
+ memset(obj, 0, sizeof(struct reftable_obj_record));
+}
+
+static int reftable_obj_record_copy_from(void *rec, const void *src_rec,
+ uint32_t hash_size REFTABLE_UNUSED)
+{
+ struct reftable_obj_record *obj = rec;
+ const struct reftable_obj_record *src = src_rec;
+
+ reftable_obj_record_release(obj);
+
+ REFTABLE_ALLOC_ARRAY(obj->hash_prefix, src->hash_prefix_len);
+ if (!obj->hash_prefix)
+ return REFTABLE_OUT_OF_MEMORY_ERROR;
+ obj->hash_prefix_len = src->hash_prefix_len;
+ if (src->hash_prefix_len)
+ memcpy(obj->hash_prefix, src->hash_prefix, obj->hash_prefix_len);
+
+ if (src->offset_len) {
+ if (sizeof(*src->offsets) > SIZE_MAX / src->offset_len)
+ return REFTABLE_OUT_OF_MEMORY_ERROR;
+
+ REFTABLE_ALLOC_ARRAY(obj->offsets, src->offset_len);
+ if (!obj->offsets)
+ return REFTABLE_OUT_OF_MEMORY_ERROR;
+
+ memcpy(obj->offsets, src->offsets, sizeof(*src->offsets) * src->offset_len);
+ obj->offset_len = src->offset_len;
+ }
+
+ return 0;
+}
+
+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,
+ uint32_t hash_size REFTABLE_UNUSED)
+{
+ 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 n;
+ 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 n;
+ 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 n;
+ string_view_consume(&s, n);
+ last = r->offsets[i];
+ }
+ return start.len - s.len;
+}
+
+static int reftable_obj_record_decode(void *rec, struct reftable_buf key,
+ uint8_t val_type, struct string_view in,
+ uint32_t hash_size REFTABLE_UNUSED,
+ struct reftable_buf *scratch REFTABLE_UNUSED)
+{
+ struct string_view start = in;
+ struct reftable_obj_record *r = rec;
+ uint64_t count = val_type;
+ int n = 0;
+ uint64_t last;
+
+ reftable_obj_record_release(r);
+
+ REFTABLE_ALLOC_ARRAY(r->hash_prefix, key.len);
+ if (!r->hash_prefix)
+ return REFTABLE_OUT_OF_MEMORY_ERROR;
+ 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);
+ if (!r->offsets)
+ return REFTABLE_OUT_OF_MEMORY_ERROR;
+ 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];
+ for (uint64_t j = 1; j < count; j++) {
+ 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);
+ }
+ return start.len - in.len;
+}
+
+static int not_a_deletion(const void *p REFTABLE_UNUSED)
+{
+ return 0;
+}
+
+static int reftable_obj_record_equal_void(const void *a, const void *b,
+ uint32_t hash_size REFTABLE_UNUSED)
+{
+ 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 int reftable_obj_record_cmp_void(const void *_a, const void *_b)
+{
+ const struct reftable_obj_record *a = _a;
+ const struct reftable_obj_record *b = _b;
+ int cmp;
+
+ cmp = memcmp(a->hash_prefix, b->hash_prefix,
+ a->hash_prefix_len > b->hash_prefix_len ?
+ a->hash_prefix_len : b->hash_prefix_len);
+ if (cmp)
+ return cmp;
+
+ /*
+ * When the prefix is the same then the object record that is longer is
+ * considered to be bigger.
+ */
+ return a->hash_prefix_len - b->hash_prefix_len;
+}
+
+static struct reftable_record_vtable reftable_obj_record_vtable = {
+ .key = &reftable_obj_record_key,
+ .type = REFTABLE_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 = &not_a_deletion,
+ .equal = &reftable_obj_record_equal_void,
+ .cmp = &reftable_obj_record_cmp_void,
+};
+
+static int reftable_log_record_key(const void *r, struct reftable_buf *dest)
+{
+ const struct reftable_log_record *rec =
+ (const struct reftable_log_record *)r;
+ int len = strlen(rec->refname), err;
+ uint8_t i64[8];
+ uint64_t ts = 0;
+
+ reftable_buf_reset(dest);
+ err = reftable_buf_add(dest, (uint8_t *)rec->refname, len + 1);
+ if (err < 0)
+ return err;
+
+ ts = (~ts) - rec->update_index;
+ reftable_put_be64(&i64[0], ts);
+
+ err = reftable_buf_add(dest, i64, sizeof(i64));
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int reftable_log_record_copy_from(void *rec, const void *src_rec,
+ uint32_t hash_size)
+{
+ struct reftable_log_record *dst = rec;
+ const struct reftable_log_record *src =
+ (const struct reftable_log_record *)src_rec;
+ int ret;
+
+ reftable_log_record_release(dst);
+ *dst = *src;
+
+ if (dst->refname) {
+ dst->refname = reftable_strdup(dst->refname);
+ if (!dst->refname) {
+ ret = REFTABLE_OUT_OF_MEMORY_ERROR;
+ goto out;
+ }
+ }
+
+ switch (dst->value_type) {
+ case REFTABLE_LOG_DELETION:
+ break;
+ case REFTABLE_LOG_UPDATE:
+ if (dst->value.update.email)
+ dst->value.update.email =
+ reftable_strdup(dst->value.update.email);
+ if (dst->value.update.name)
+ dst->value.update.name =
+ reftable_strdup(dst->value.update.name);
+ if (dst->value.update.message)
+ dst->value.update.message =
+ reftable_strdup(dst->value.update.message);
+
+ if (!dst->value.update.email ||
+ !dst->value.update.name ||
+ !dst->value.update.message) {
+ ret = REFTABLE_OUT_OF_MEMORY_ERROR;
+ goto out;
+ }
+
+ memcpy(dst->value.update.new_hash,
+ src->value.update.new_hash, hash_size);
+ memcpy(dst->value.update.old_hash,
+ src->value.update.old_hash, hash_size);
+ break;
+ }
+
+ ret = 0;
+out:
+ return ret;
+}
+
+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.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 int reftable_log_record_encode(const void *rec, struct string_view s,
+ uint32_t hash_size)
+{
+ const struct reftable_log_record *r = rec;
+ struct string_view start = s;
+ int n = 0;
+ if (reftable_log_record_is_deletion(r))
+ return 0;
+
+ if (s.len < 2 * hash_size)
+ return REFTABLE_ENTRY_TOO_BIG_ERROR;
+
+ memcpy(s.buf, r->value.update.old_hash, hash_size);
+ memcpy(s.buf + hash_size, r->value.update.new_hash, 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 n;
+ string_view_consume(&s, n);
+
+ n = encode_string(r->value.update.email ? r->value.update.email : "",
+ s);
+ if (n < 0)
+ return n;
+ string_view_consume(&s, n);
+
+ n = put_var_int(&s, r->value.update.time);
+ if (n < 0)
+ return n;
+ string_view_consume(&s, n);
+
+ if (s.len < 2)
+ return REFTABLE_ENTRY_TOO_BIG_ERROR;
+
+ reftable_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 n;
+ string_view_consume(&s, n);
+
+ return start.len - s.len;
+}
+
+static int reftable_log_record_decode(void *rec, struct reftable_buf key,
+ uint8_t val_type, struct string_view in,
+ uint32_t hash_size, struct reftable_buf *scratch)
+{
+ struct string_view start = in;
+ struct reftable_log_record *r = rec;
+ uint64_t max = 0;
+ uint64_t ts = 0;
+ int err, n;
+
+ if (key.len <= 9 || key.buf[key.len - 9] != 0)
+ return REFTABLE_FORMAT_ERROR;
+
+ REFTABLE_ALLOC_GROW_OR_NULL(r->refname, key.len - 8, r->refname_cap);
+ if (!r->refname) {
+ err = REFTABLE_OUT_OF_MEMORY_ERROR;
+ goto done;
+ }
+
+ memcpy(r->refname, key.buf, key.len - 8);
+ ts = reftable_get_be64((unsigned char *)key.buf + key.len - 8);
+
+ r->update_index = (~max) - ts;
+
+ if (val_type != r->value_type) {
+ switch (r->value_type) {
+ case REFTABLE_LOG_UPDATE:
+ REFTABLE_FREE_AND_NULL(r->value.update.message);
+ r->value.update.message_cap = 0;
+ REFTABLE_FREE_AND_NULL(r->value.update.email);
+ REFTABLE_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) {
+ err = REFTABLE_FORMAT_ERROR;
+ goto done;
+ }
+
+ 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(scratch, in);
+ if (n < 0) {
+ err = REFTABLE_FORMAT_ERROR;
+ goto done;
+ }
+ string_view_consume(&in, n);
+
+ /*
+ * In almost all cases we can expect the reflog name to not change for
+ * reflog entries as they are tied to the local identity, not to the
+ * target commits. As an optimization for this common case we can thus
+ * skip copying over the name in case it's accurate already.
+ */
+ if (!r->value.update.name ||
+ strcmp(r->value.update.name, scratch->buf)) {
+ char *name = reftable_realloc(r->value.update.name, scratch->len + 1);
+ if (!name) {
+ err = REFTABLE_OUT_OF_MEMORY_ERROR;
+ goto done;
+ }
+
+ r->value.update.name = name;
+ memcpy(r->value.update.name, scratch->buf, scratch->len);
+ r->value.update.name[scratch->len] = 0;
+ }
+
+ n = decode_string(scratch, in);
+ if (n < 0) {
+ err = REFTABLE_FORMAT_ERROR;
+ goto done;
+ }
+ string_view_consume(&in, n);
+
+ /* Same as above, but for the reflog email. */
+ if (!r->value.update.email ||
+ strcmp(r->value.update.email, scratch->buf)) {
+ char *email = reftable_realloc(r->value.update.email, scratch->len + 1);
+ if (!email) {
+ err = REFTABLE_OUT_OF_MEMORY_ERROR;
+ goto done;
+ }
+
+ r->value.update.email = email;
+ memcpy(r->value.update.email, scratch->buf, scratch->len);
+ r->value.update.email[scratch->len] = 0;
+ }
+
+ ts = 0;
+ n = get_var_int(&ts, &in);
+ if (n < 0) {
+ err = REFTABLE_FORMAT_ERROR;
+ goto done;
+ }
+ string_view_consume(&in, n);
+ r->value.update.time = ts;
+ if (in.len < 2) {
+ err = REFTABLE_FORMAT_ERROR;
+ goto done;
+ }
+
+ r->value.update.tz_offset = reftable_get_be16(in.buf);
+ string_view_consume(&in, 2);
+
+ n = decode_string(scratch, in);
+ if (n < 0) {
+ err = REFTABLE_FORMAT_ERROR;
+ goto done;
+ }
+ string_view_consume(&in, n);
+
+ REFTABLE_ALLOC_GROW_OR_NULL(r->value.update.message, scratch->len + 1,
+ r->value.update.message_cap);
+ if (!r->value.update.message) {
+ err = REFTABLE_OUT_OF_MEMORY_ERROR;
+ goto done;
+ }
+
+ memcpy(r->value.update.message, scratch->buf, scratch->len);
+ r->value.update.message[scratch->len] = 0;
+
+ return start.len - in.len;
+
+done:
+ return err;
+}
+
+static int null_streq(const char *a, const char *b)
+{
+ const char *empty = "";
+ if (!a)
+ a = empty;
+
+ if (!b)
+ b = empty;
+
+ return 0 == strcmp(a, b);
+}
+
+static int reftable_log_record_equal_void(const void *a,
+ const void *b, uint32_t hash_size)
+{
+ return reftable_log_record_equal((struct reftable_log_record *) a,
+ (struct reftable_log_record *) b,
+ hash_size);
+}
+
+static int reftable_log_record_cmp_void(const void *_a, const void *_b)
+{
+ const struct reftable_log_record *a = _a;
+ const struct reftable_log_record *b = _b;
+ int cmp = strcmp(a->refname, b->refname);
+ if (cmp)
+ return cmp;
+
+ /*
+ * Note that the comparison here is reversed. This is because the
+ * update index is reversed when comparing keys. For reference, see how
+ * we handle this in reftable_log_record_key()`.
+ */
+ return b->update_index - a->update_index;
+}
+
+int reftable_log_record_equal(const struct reftable_log_record *a,
+ const struct reftable_log_record *b, uint32_t 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) &&
+ !memcmp(a->value.update.old_hash,
+ b->value.update.old_hash, hash_size) &&
+ !memcmp(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 struct reftable_record_vtable reftable_log_record_vtable = {
+ .key = &reftable_log_record_key,
+ .type = REFTABLE_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,
+ .cmp = &reftable_log_record_cmp_void,
+};
+
+static int reftable_index_record_key(const void *r, struct reftable_buf *dest)
+{
+ const struct reftable_index_record *rec = r;
+ reftable_buf_reset(dest);
+ return reftable_buf_add(dest, rec->last_key.buf, rec->last_key.len);
+}
+
+static int reftable_index_record_copy_from(void *rec, const void *src_rec,
+ uint32_t hash_size REFTABLE_UNUSED)
+{
+ struct reftable_index_record *dst = rec;
+ const struct reftable_index_record *src = src_rec;
+ int err;
+
+ reftable_buf_reset(&dst->last_key);
+ err = reftable_buf_add(&dst->last_key, src->last_key.buf, src->last_key.len);
+ if (err < 0)
+ return err;
+ dst->offset = src->offset;
+
+ return 0;
+}
+
+static void reftable_index_record_release(void *rec)
+{
+ struct reftable_index_record *idx = rec;
+ reftable_buf_release(&idx->last_key);
+}
+
+static uint8_t reftable_index_record_val_type(const void *rec REFTABLE_UNUSED)
+{
+ return 0;
+}
+
+static int reftable_index_record_encode(const void *rec, struct string_view out,
+ uint32_t hash_size REFTABLE_UNUSED)
+{
+ 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 reftable_buf key,
+ uint8_t val_type REFTABLE_UNUSED,
+ struct string_view in,
+ uint32_t hash_size REFTABLE_UNUSED,
+ struct reftable_buf *scratch REFTABLE_UNUSED)
+{
+ struct string_view start = in;
+ struct reftable_index_record *r = rec;
+ int err, n = 0;
+
+ reftable_buf_reset(&r->last_key);
+ err = reftable_buf_add(&r->last_key, key.buf, key.len);
+ if (err < 0)
+ return err;
+
+ 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,
+ uint32_t hash_size REFTABLE_UNUSED)
+{
+ 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 && !reftable_buf_cmp(&ia->last_key, &ib->last_key);
+}
+
+static int reftable_index_record_cmp(const void *_a, const void *_b)
+{
+ const struct reftable_index_record *a = _a;
+ const struct reftable_index_record *b = _b;
+ return reftable_buf_cmp(&a->last_key, &b->last_key);
+}
+
+static struct reftable_record_vtable reftable_index_record_vtable = {
+ .key = &reftable_index_record_key,
+ .type = REFTABLE_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 = &not_a_deletion,
+ .equal = &reftable_index_record_equal,
+ .cmp = &reftable_index_record_cmp,
+};
+
+int reftable_record_key(struct reftable_record *rec, struct reftable_buf *dest)
+{
+ return reftable_record_vtable(rec)->key(reftable_record_data(rec), dest);
+}
+
+int reftable_record_encode(struct reftable_record *rec, struct string_view dest,
+ uint32_t hash_size)
+{
+ return reftable_record_vtable(rec)->encode(reftable_record_data(rec),
+ dest, hash_size);
+}
+
+int reftable_record_copy_from(struct reftable_record *rec,
+ struct reftable_record *src, uint32_t hash_size)
+{
+ assert(src->type == rec->type);
+
+ return 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 reftable_buf key,
+ uint8_t extra, struct string_view src, uint32_t hash_size,
+ struct reftable_buf *scratch)
+{
+ return reftable_record_vtable(rec)->decode(reftable_record_data(rec),
+ key, extra, src, hash_size,
+ scratch);
+}
+
+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_cmp(struct reftable_record *a, struct reftable_record *b,
+ int *cmp)
+{
+ if (a->type != b->type)
+ return -1;
+ *cmp = reftable_record_vtable(a)->cmp(reftable_record_data(a),
+ reftable_record_data(b));
+ return 0;
+}
+
+int reftable_record_equal(struct reftable_record *a, struct reftable_record *b, uint32_t 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, uint32_t 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, uint32_t hash_size)
+{
+ 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);
+}
+
+static void *reftable_record_data(struct reftable_record *rec)
+{
+ switch (rec->type) {
+ case REFTABLE_BLOCK_TYPE_REF:
+ return &rec->u.ref;
+ case REFTABLE_BLOCK_TYPE_LOG:
+ return &rec->u.log;
+ case REFTABLE_BLOCK_TYPE_INDEX:
+ return &rec->u.idx;
+ case REFTABLE_BLOCK_TYPE_OBJ:
+ return &rec->u.obj;
+ }
+ abort();
+}
+
+static struct reftable_record_vtable *
+reftable_record_vtable(struct reftable_record *rec)
+{
+ switch (rec->type) {
+ case REFTABLE_BLOCK_TYPE_REF:
+ return &reftable_ref_record_vtable;
+ case REFTABLE_BLOCK_TYPE_LOG:
+ return &reftable_log_record_vtable;
+ case REFTABLE_BLOCK_TYPE_INDEX:
+ return &reftable_index_record_vtable;
+ case REFTABLE_BLOCK_TYPE_OBJ:
+ return &reftable_obj_record_vtable;
+ }
+ abort();
+}
+
+int reftable_record_init(struct reftable_record *rec, uint8_t typ)
+{
+ memset(rec, 0, sizeof(*rec));
+ rec->type = typ;
+
+ switch (typ) {
+ case REFTABLE_BLOCK_TYPE_REF:
+ case REFTABLE_BLOCK_TYPE_LOG:
+ case REFTABLE_BLOCK_TYPE_OBJ:
+ return 0;
+ case REFTABLE_BLOCK_TYPE_INDEX:
+ reftable_buf_init(&rec->u.idx.last_key);
+ return 0;
+ default:
+ return REFTABLE_API_ERROR;
+ }
+}