210 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			C
		
	
	
			
		
		
	
	
			210 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			C
		
	
	
| /*
 | |
|   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;
 | |
| }
 |