blob: bbfde15754a227c9bd418572b778d77827a59db4 [file] [log] [blame]
/*
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
*/
#include "system.h"
#include "reftable-error.h"
#include "basics.h"
#include "refname.h"
#include "reftable-iterator.h"
struct refname_needle_lesseq_args {
char **haystack;
const char *needle;
};
static int refname_needle_lesseq(size_t k, void *_args)
{
struct refname_needle_lesseq_args *args = _args;
return strcmp(args->needle, args->haystack[k]) <= 0;
}
static int modification_has_ref(struct modification *mod, const char *name)
{
struct reftable_ref_record ref = { NULL };
int err = 0;
if (mod->add_len > 0) {
struct refname_needle_lesseq_args args = {
.haystack = mod->add,
.needle = name,
};
size_t idx = binsearch(mod->add_len, refname_needle_lesseq, &args);
if (idx < mod->add_len && !strcmp(mod->add[idx], name))
return 0;
}
if (mod->del_len > 0) {
struct refname_needle_lesseq_args args = {
.haystack = mod->del,
.needle = name,
};
size_t idx = binsearch(mod->del_len, refname_needle_lesseq, &args);
if (idx < mod->del_len && !strcmp(mod->del[idx], name))
return 1;
}
err = reftable_table_read_ref(&mod->tab, name, &ref);
reftable_ref_record_release(&ref);
return err;
}
static void modification_release(struct modification *mod)
{
/* don't delete the strings themselves; they're owned by ref records.
*/
FREE_AND_NULL(mod->add);
FREE_AND_NULL(mod->del);
mod->add_len = 0;
mod->del_len = 0;
}
static int modification_has_ref_with_prefix(struct modification *mod,
const char *prefix)
{
struct reftable_iterator it = { NULL };
struct reftable_ref_record ref = { NULL };
int err = 0;
if (mod->add_len > 0) {
struct refname_needle_lesseq_args args = {
.haystack = mod->add,
.needle = prefix,
};
size_t idx = binsearch(mod->add_len, refname_needle_lesseq, &args);
if (idx < mod->add_len &&
!strncmp(prefix, mod->add[idx], strlen(prefix)))
goto done;
}
err = reftable_table_seek_ref(&mod->tab, &it, prefix);
if (err)
goto done;
while (1) {
err = reftable_iterator_next_ref(&it, &ref);
if (err)
goto done;
if (mod->del_len > 0) {
struct refname_needle_lesseq_args args = {
.haystack = mod->del,
.needle = ref.refname,
};
size_t idx = binsearch(mod->del_len, refname_needle_lesseq, &args);
if (idx < mod->del_len &&
!strcmp(ref.refname, mod->del[idx]))
continue;
}
if (strncmp(ref.refname, prefix, strlen(prefix))) {
err = 1;
goto done;
}
err = 0;
goto done;
}
done:
reftable_ref_record_release(&ref);
reftable_iterator_destroy(&it);
return err;
}
static int validate_refname(const char *name)
{
while (1) {
char *next = strchr(name, '/');
if (!*name) {
return REFTABLE_REFNAME_ERROR;
}
if (!next) {
return 0;
}
if (next - name == 0 || (next - name == 1 && *name == '.') ||
(next - name == 2 && name[0] == '.' && name[1] == '.'))
return REFTABLE_REFNAME_ERROR;
name = next + 1;
}
return 0;
}
int validate_ref_record_addition(struct reftable_table tab,
struct reftable_ref_record *recs, size_t sz)
{
struct modification mod = {
.tab = tab,
.add = reftable_calloc(sz, sizeof(*mod.add)),
.del = reftable_calloc(sz, sizeof(*mod.del)),
};
int i = 0;
int err = 0;
for (; i < sz; i++) {
if (reftable_ref_record_is_deletion(&recs[i])) {
mod.del[mod.del_len++] = recs[i].refname;
} else {
mod.add[mod.add_len++] = recs[i].refname;
}
}
err = modification_validate(&mod);
modification_release(&mod);
return err;
}
static void strbuf_trim_component(struct strbuf *sl)
{
while (sl->len > 0) {
int is_slash = (sl->buf[sl->len - 1] == '/');
strbuf_setlen(sl, sl->len - 1);
if (is_slash)
break;
}
}
int modification_validate(struct modification *mod)
{
struct strbuf slashed = STRBUF_INIT;
int err = 0;
int i = 0;
for (; i < mod->add_len; i++) {
err = validate_refname(mod->add[i]);
if (err)
goto done;
strbuf_reset(&slashed);
strbuf_addstr(&slashed, mod->add[i]);
strbuf_addstr(&slashed, "/");
err = modification_has_ref_with_prefix(mod, slashed.buf);
if (err == 0) {
err = REFTABLE_NAME_CONFLICT;
goto done;
}
if (err < 0)
goto done;
strbuf_reset(&slashed);
strbuf_addstr(&slashed, mod->add[i]);
while (slashed.len) {
strbuf_trim_component(&slashed);
err = modification_has_ref(mod, slashed.buf);
if (err == 0) {
err = REFTABLE_NAME_CONFLICT;
goto done;
}
if (err < 0)
goto done;
}
}
err = 0;
done:
strbuf_release(&slashed);
return err;
}