/* 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; }