| /* | |
| 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 find_arg { | |
| char **names; | |
| const char *want; | |
| }; | |
| static int find_name(size_t k, void *arg) | |
| { | |
| struct find_arg *f_arg = arg; | |
| return strcmp(f_arg->names[k], f_arg->want) >= 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 find_arg arg = { | |
| .names = mod->add, | |
| .want = name, | |
| }; | |
| int idx = binsearch(mod->add_len, find_name, &arg); | |
| if (idx < mod->add_len && !strcmp(mod->add[idx], name)) { | |
| return 0; | |
| } | |
| } | |
| if (mod->del_len > 0) { | |
| struct find_arg arg = { | |
| .names = mod->del, | |
| .want = name, | |
| }; | |
| int idx = binsearch(mod->del_len, find_name, &arg); | |
| 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 find_arg arg = { | |
| .names = mod->add, | |
| .want = prefix, | |
| }; | |
| int idx = binsearch(mod->add_len, find_name, &arg); | |
| 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 find_arg arg = { | |
| .names = mod->del, | |
| .want = ref.refname, | |
| }; | |
| int idx = binsearch(mod->del_len, find_name, &arg); | |
| 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(sizeof(char *) * sz), | |
| .del = reftable_calloc(sizeof(char *) * sz), | |
| }; | |
| 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; | |
| } |