Skip to content

Commit

Permalink
reftable: prevent 'update_index' changes after adding records
Browse files Browse the repository at this point in the history
The function `reftable_writer_set_limits()` allows updating the
'min_update_index' and 'max_update_index' of a reftable writer. These
values are written to both the writer's header and footer.

Since the header is written during the first block write, any subsequent
changes to the update index would create a mismatch between the header
and footer values. The footer would contain the newer values while the
header retained the original ones.

To fix this bug, prevent callers from updating these values after any
record is written. To do this, modify the function to return an error
whenever the limits are modified after any record adds. Check for record
adds within `reftable_writer_set_limits()` by checking the `last_key`
variable, which is set whenever a new record is added.

Modify all callers of the function to anticipate a return type and
handle it accordingly.

Helped-by: Patrick Steinhardt <[email protected]>
Signed-off-by: Karthik Nayak <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
  • Loading branch information
KarthikNayak authored and gitster committed Jan 21, 2025
1 parent 007449a commit 2aeef66
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 22 deletions.
20 changes: 15 additions & 5 deletions refs/reftable-backend.c
Original file line number Diff line number Diff line change
Expand Up @@ -1443,7 +1443,9 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
* multiple entries. Each entry will contain a different update_index,
* so set the limits accordingly.
*/
reftable_writer_set_limits(writer, ts, ts + arg->max_index);
ret = reftable_writer_set_limits(writer, ts, ts + arg->max_index);
if (ret < 0)
goto done;

for (i = 0; i < arg->updates_nr; i++) {
struct reftable_transaction_update *tx_update = &arg->updates[i];
Expand Down Expand Up @@ -1766,7 +1768,9 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
deletion_ts = creation_ts = reftable_stack_next_update_index(arg->be->stack);
if (arg->delete_old)
creation_ts++;
reftable_writer_set_limits(writer, deletion_ts, creation_ts);
ret = reftable_writer_set_limits(writer, deletion_ts, creation_ts);
if (ret < 0)
goto done;

/*
* Add the new reference. If this is a rename then we also delete the
Expand Down Expand Up @@ -2298,7 +2302,9 @@ static int write_reflog_existence_table(struct reftable_writer *writer,
if (ret <= 0)
goto done;

reftable_writer_set_limits(writer, ts, ts);
ret = reftable_writer_set_limits(writer, ts, ts);
if (ret < 0)
goto done;

/*
* The existence entry has both old and new object ID set to the
Expand Down Expand Up @@ -2357,7 +2363,9 @@ static int write_reflog_delete_table(struct reftable_writer *writer, void *cb_da
uint64_t ts = reftable_stack_next_update_index(arg->stack);
int ret;

reftable_writer_set_limits(writer, ts, ts);
ret = reftable_writer_set_limits(writer, ts, ts);
if (ret < 0)
goto out;

ret = reftable_stack_init_log_iterator(arg->stack, &it);
if (ret < 0)
Expand Down Expand Up @@ -2434,7 +2442,9 @@ static int write_reflog_expiry_table(struct reftable_writer *writer, void *cb_da
if (arg->records[i].value_type == REFTABLE_LOG_UPDATE)
live_records++;

reftable_writer_set_limits(writer, ts, ts);
ret = reftable_writer_set_limits(writer, ts, ts);
if (ret < 0)
return ret;

if (!is_null_oid(&arg->update_oid)) {
struct reftable_ref_record ref = {0};
Expand Down
1 change: 1 addition & 0 deletions reftable/reftable-error.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ enum reftable_error {

/* Misuse of the API:
* - on writing a record with NULL refname.
* - on writing a record before setting the writer limits.
* - on writing a reftable_ref_record outside the table limits
* - on writing a ref or log record before the stack's
* next_update_inde*x
Expand Down
24 changes: 14 additions & 10 deletions reftable/reftable-writer.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,17 +124,21 @@ int reftable_writer_new(struct reftable_writer **out,
int (*flush_func)(void *),
void *writer_arg, const struct reftable_write_options *opts);

/* Set the range of update indices for the records we will add. When writing a
table into a stack, the min should be at least
reftable_stack_next_update_index(), or REFTABLE_API_ERROR is returned.
For transactional updates to a stack, typically min==max, and the
update_index can be obtained by inspeciting the stack. When converting an
existing ref database into a single reftable, this would be a range of
update-index timestamps.
/*
* Set the range of update indices for the records we will add. When writing a
* table into a stack, the min should be at least
* reftable_stack_next_update_index(), or REFTABLE_API_ERROR is returned.
*
* For transactional updates to a stack, typically min==max, and the
* update_index can be obtained by inspeciting the stack. When converting an
* existing ref database into a single reftable, this would be a range of
* update-index timestamps.
*
* The function should be called before adding any records to the writer. If not
* it will fail with REFTABLE_API_ERROR.
*/
void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,
uint64_t max);
int reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,
uint64_t max);

/*
Add a reftable_ref_record. The record should have names that come after
Expand Down
6 changes: 4 additions & 2 deletions reftable/stack.c
Original file line number Diff line number Diff line change
Expand Up @@ -1058,8 +1058,10 @@ static int stack_write_compact(struct reftable_stack *st,

for (size_t i = first; i <= last; i++)
st->stats.bytes += st->readers[i]->size;
reftable_writer_set_limits(wr, st->readers[first]->min_update_index,
st->readers[last]->max_update_index);
err = reftable_writer_set_limits(wr, st->readers[first]->min_update_index,
st->readers[last]->max_update_index);
if (err < 0)
goto done;

err = reftable_merged_table_new(&mt, st->readers + first, subtabs_len,
st->opts.hash_id);
Expand Down
13 changes: 11 additions & 2 deletions reftable/writer.c
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,20 @@ int reftable_writer_new(struct reftable_writer **out,
return 0;
}

void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,
uint64_t max)
int reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,
uint64_t max)
{
/*
* The limits should be set before any records are added to the writer.
* Check if any records were added by checking if `last_key` was set.
*/
if (w->last_key.len)
return REFTABLE_API_ERROR;

w->min_update_index = min;
w->max_update_index = max;

return 0;
}

static void writer_release(struct reftable_writer *w)
Expand Down
8 changes: 5 additions & 3 deletions t/unit-tests/t-reftable-stack.c
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ static void t_read_file(void)
static int write_test_ref(struct reftable_writer *wr, void *arg)
{
struct reftable_ref_record *ref = arg;
reftable_writer_set_limits(wr, ref->update_index, ref->update_index);
check(!reftable_writer_set_limits(wr, ref->update_index,
ref->update_index));
return reftable_writer_add_ref(wr, ref);
}

Expand Down Expand Up @@ -143,7 +144,8 @@ static int write_test_log(struct reftable_writer *wr, void *arg)
{
struct write_log_arg *wla = arg;

reftable_writer_set_limits(wr, wla->update_index, wla->update_index);
check(!reftable_writer_set_limits(wr, wla->update_index,
wla->update_index));
return reftable_writer_add_log(wr, wla->log);
}

Expand Down Expand Up @@ -961,7 +963,7 @@ static void t_reflog_expire(void)

static int write_nothing(struct reftable_writer *wr, void *arg UNUSED)
{
reftable_writer_set_limits(wr, 1, 1);
check(!reftable_writer_set_limits(wr, 1, 1));
return 0;
}

Expand Down

0 comments on commit 2aeef66

Please sign in to comment.