Blame src/sortedcache.c

Packit ae9e2a
#include "sortedcache.h"
Packit ae9e2a
Packit ae9e2a
int git_sortedcache_new(
Packit ae9e2a
	git_sortedcache **out,
Packit ae9e2a
	size_t item_path_offset,
Packit ae9e2a
	git_sortedcache_free_item_fn free_item,
Packit ae9e2a
	void *free_item_payload,
Packit ae9e2a
	git_vector_cmp item_cmp,
Packit ae9e2a
	const char *path)
Packit ae9e2a
{
Packit ae9e2a
	git_sortedcache *sc;
Packit ae9e2a
	size_t pathlen, alloclen;
Packit ae9e2a
Packit ae9e2a
	pathlen = path ? strlen(path) : 0;
Packit ae9e2a
Packit ae9e2a
	GITERR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_sortedcache), pathlen);
Packit ae9e2a
	GITERR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
Packit ae9e2a
	sc = git__calloc(1, alloclen);
Packit ae9e2a
	GITERR_CHECK_ALLOC(sc);
Packit ae9e2a
Packit ae9e2a
	git_pool_init(&sc->pool, 1);
Packit ae9e2a
Packit ae9e2a
	if (git_vector_init(&sc->items, 4, item_cmp) < 0 ||
Packit ae9e2a
		git_strmap_alloc(&sc->map) < 0)
Packit ae9e2a
		goto fail;
Packit ae9e2a
Packit ae9e2a
	if (git_rwlock_init(&sc->lock)) {
Packit ae9e2a
		giterr_set(GITERR_OS, "failed to initialize lock");
Packit ae9e2a
		goto fail;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	sc->item_path_offset  = item_path_offset;
Packit ae9e2a
	sc->free_item         = free_item;
Packit ae9e2a
	sc->free_item_payload = free_item_payload;
Packit ae9e2a
	GIT_REFCOUNT_INC(sc);
Packit ae9e2a
	if (pathlen)
Packit ae9e2a
		memcpy(sc->path, path, pathlen);
Packit ae9e2a
Packit ae9e2a
	*out = sc;
Packit ae9e2a
	return 0;
Packit ae9e2a
Packit ae9e2a
fail:
Packit ae9e2a
	git_strmap_free(sc->map);
Packit ae9e2a
	git_vector_free(&sc->items);
Packit ae9e2a
	git_pool_clear(&sc->pool);
Packit ae9e2a
	git__free(sc);
Packit ae9e2a
	return -1;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
void git_sortedcache_incref(git_sortedcache *sc)
Packit ae9e2a
{
Packit ae9e2a
	GIT_REFCOUNT_INC(sc);
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
const char *git_sortedcache_path(git_sortedcache *sc)
Packit ae9e2a
{
Packit ae9e2a
	return sc->path;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
static void sortedcache_clear(git_sortedcache *sc)
Packit ae9e2a
{
Packit ae9e2a
	git_strmap_clear(sc->map);
Packit ae9e2a
Packit ae9e2a
	if (sc->free_item) {
Packit ae9e2a
		size_t i;
Packit ae9e2a
		void *item;
Packit ae9e2a
Packit ae9e2a
		git_vector_foreach(&sc->items, i, item) {
Packit ae9e2a
			sc->free_item(sc->free_item_payload, item);
Packit ae9e2a
		}
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	git_vector_clear(&sc->items);
Packit ae9e2a
Packit ae9e2a
	git_pool_clear(&sc->pool);
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
static void sortedcache_free(git_sortedcache *sc)
Packit ae9e2a
{
Packit ae9e2a
	/* acquire write lock to make sure everyone else is done */
Packit ae9e2a
	if (git_sortedcache_wlock(sc) < 0)
Packit ae9e2a
		return;
Packit ae9e2a
Packit ae9e2a
	sortedcache_clear(sc);
Packit ae9e2a
	git_vector_free(&sc->items);
Packit ae9e2a
	git_strmap_free(sc->map);
Packit ae9e2a
Packit ae9e2a
	git_sortedcache_wunlock(sc);
Packit ae9e2a
Packit ae9e2a
	git_rwlock_free(&sc->lock);
Packit ae9e2a
	git__free(sc);
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
void git_sortedcache_free(git_sortedcache *sc)
Packit ae9e2a
{
Packit ae9e2a
	if (!sc)
Packit ae9e2a
		return;
Packit ae9e2a
	GIT_REFCOUNT_DEC(sc, sortedcache_free);
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
static int sortedcache_copy_item(void *payload, void *tgt_item, void *src_item)
Packit ae9e2a
{
Packit ae9e2a
	git_sortedcache *sc = payload;
Packit ae9e2a
	/* path will already have been copied by upsert */
Packit ae9e2a
	memcpy(tgt_item, src_item, sc->item_path_offset);
Packit ae9e2a
	return 0;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
/* copy a sorted cache */
Packit ae9e2a
int git_sortedcache_copy(
Packit ae9e2a
	git_sortedcache **out,
Packit ae9e2a
	git_sortedcache *src,
Packit ae9e2a
	bool lock,
Packit ae9e2a
	int (*copy_item)(void *payload, void *tgt_item, void *src_item),
Packit ae9e2a
	void *payload)
Packit ae9e2a
{
Packit ae9e2a
	int error = 0;
Packit ae9e2a
	git_sortedcache *tgt;
Packit ae9e2a
	size_t i;
Packit ae9e2a
	void *src_item, *tgt_item;
Packit ae9e2a
Packit ae9e2a
	/* just use memcpy if no special copy fn is passed in */
Packit ae9e2a
	if (!copy_item) {
Packit ae9e2a
		copy_item = sortedcache_copy_item;
Packit ae9e2a
		payload   = src;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	if ((error = git_sortedcache_new(
Packit ae9e2a
			&tgt, src->item_path_offset,
Packit ae9e2a
			src->free_item, src->free_item_payload,
Packit ae9e2a
			src->items._cmp, src->path)) < 0)
Packit ae9e2a
		return error;
Packit ae9e2a
Packit ae9e2a
	if (lock && git_sortedcache_rlock(src) < 0) {
Packit ae9e2a
		git_sortedcache_free(tgt);
Packit ae9e2a
		return -1;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	git_vector_foreach(&src->items, i, src_item) {
Packit ae9e2a
		char *path = ((char *)src_item) + src->item_path_offset;
Packit ae9e2a
Packit ae9e2a
		if ((error = git_sortedcache_upsert(&tgt_item, tgt, path)) < 0 ||
Packit ae9e2a
			(error = copy_item(payload, tgt_item, src_item)) < 0)
Packit ae9e2a
			break;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	if (lock)
Packit ae9e2a
		git_sortedcache_runlock(src);
Packit ae9e2a
	if (error)
Packit ae9e2a
		git_sortedcache_free(tgt);
Packit ae9e2a
Packit ae9e2a
	*out = !error ? tgt : NULL;
Packit ae9e2a
Packit ae9e2a
	return error;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
/* lock sortedcache while making modifications */
Packit ae9e2a
int git_sortedcache_wlock(git_sortedcache *sc)
Packit ae9e2a
{
Packit ae9e2a
	GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */
Packit ae9e2a
Packit ae9e2a
	if (git_rwlock_wrlock(&sc->lock) < 0) {
Packit ae9e2a
		giterr_set(GITERR_OS, "unable to acquire write lock on cache");
Packit ae9e2a
		return -1;
Packit ae9e2a
	}
Packit ae9e2a
	return 0;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
/* unlock sorted cache when done with modifications */
Packit ae9e2a
void git_sortedcache_wunlock(git_sortedcache *sc)
Packit ae9e2a
{
Packit ae9e2a
	git_vector_sort(&sc->items);
Packit ae9e2a
	git_rwlock_wrunlock(&sc->lock);
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
/* lock sortedcache for read */
Packit ae9e2a
int git_sortedcache_rlock(git_sortedcache *sc)
Packit ae9e2a
{
Packit ae9e2a
	GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */
Packit ae9e2a
Packit ae9e2a
	if (git_rwlock_rdlock(&sc->lock) < 0) {
Packit ae9e2a
		giterr_set(GITERR_OS, "unable to acquire read lock on cache");
Packit ae9e2a
		return -1;
Packit ae9e2a
	}
Packit ae9e2a
	return 0;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
/* unlock sorted cache when done reading */
Packit ae9e2a
void git_sortedcache_runlock(git_sortedcache *sc)
Packit ae9e2a
{
Packit ae9e2a
	GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */
Packit ae9e2a
	git_rwlock_rdunlock(&sc->lock);
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
/* if the file has changed, lock cache and load file contents into buf;
Packit ae9e2a
 * returns <0 on error, >0 if file has not changed
Packit ae9e2a
 */
Packit ae9e2a
int git_sortedcache_lockandload(git_sortedcache *sc, git_buf *buf)
Packit ae9e2a
{
Packit ae9e2a
	int error, fd;
Packit ae9e2a
	struct stat st;
Packit ae9e2a
Packit ae9e2a
	if ((error = git_sortedcache_wlock(sc)) < 0)
Packit ae9e2a
		return error;
Packit ae9e2a
Packit ae9e2a
	if ((error = git_futils_filestamp_check(&sc->stamp, sc->path)) <= 0)
Packit ae9e2a
		goto unlock;
Packit ae9e2a
Packit ae9e2a
	if ((fd = git_futils_open_ro(sc->path)) < 0) {
Packit ae9e2a
		error = fd;
Packit ae9e2a
		goto unlock;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	if (p_fstat(fd, &st) < 0) {
Packit ae9e2a
		giterr_set(GITERR_OS, "failed to stat file");
Packit ae9e2a
		error = -1;
Packit ae9e2a
		(void)p_close(fd);
Packit ae9e2a
		goto unlock;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	if (!git__is_sizet(st.st_size)) {
Packit ae9e2a
		giterr_set(GITERR_INVALID, "unable to load file larger than size_t");
Packit ae9e2a
		error = -1;
Packit ae9e2a
		(void)p_close(fd);
Packit ae9e2a
		goto unlock;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	if (buf)
Packit ae9e2a
		error = git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size);
Packit ae9e2a
Packit ae9e2a
	(void)p_close(fd);
Packit ae9e2a
Packit ae9e2a
	if (error < 0)
Packit ae9e2a
		goto unlock;
Packit ae9e2a
Packit ae9e2a
	return 1; /* return 1 -> file needs reload and was successfully loaded */
Packit ae9e2a
Packit ae9e2a
unlock:
Packit ae9e2a
	git_sortedcache_wunlock(sc);
Packit ae9e2a
	return error;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
void git_sortedcache_updated(git_sortedcache *sc)
Packit ae9e2a
{
Packit ae9e2a
	/* update filestamp to latest value */
Packit ae9e2a
	git_futils_filestamp_check(&sc->stamp, sc->path);
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
/* release all items in sorted cache */
Packit ae9e2a
int git_sortedcache_clear(git_sortedcache *sc, bool wlock)
Packit ae9e2a
{
Packit ae9e2a
	if (wlock && git_sortedcache_wlock(sc) < 0)
Packit ae9e2a
		return -1;
Packit ae9e2a
Packit ae9e2a
	sortedcache_clear(sc);
Packit ae9e2a
Packit ae9e2a
	if (wlock)
Packit ae9e2a
		git_sortedcache_wunlock(sc);
Packit ae9e2a
Packit ae9e2a
	return 0;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
/* find and/or insert item, returning pointer to item data */
Packit ae9e2a
int git_sortedcache_upsert(void **out, git_sortedcache *sc, const char *key)
Packit ae9e2a
{
Packit ae9e2a
	int error = 0;
Packit ae9e2a
	khiter_t pos;
Packit ae9e2a
	void *item;
Packit ae9e2a
	size_t keylen, itemlen;
Packit ae9e2a
	char *item_key;
Packit ae9e2a
Packit ae9e2a
	pos = git_strmap_lookup_index(sc->map, key);
Packit ae9e2a
	if (git_strmap_valid_index(sc->map, pos)) {
Packit ae9e2a
		item = git_strmap_value_at(sc->map, pos);
Packit ae9e2a
		goto done;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	keylen  = strlen(key);
Packit ae9e2a
	itemlen = sc->item_path_offset + keylen + 1;
Packit ae9e2a
	itemlen = (itemlen + 7) & ~7;
Packit ae9e2a
Packit ae9e2a
	if ((item = git_pool_mallocz(&sc->pool, (uint32_t)itemlen)) == NULL) {
Packit ae9e2a
		/* don't use GITERR_CHECK_ALLOC b/c of lock */
Packit ae9e2a
		error = -1;
Packit ae9e2a
		goto done;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	/* one strange thing is that even if the vector or hash table insert
Packit ae9e2a
	 * fail, there is no way to free the pool item so we just abandon it
Packit ae9e2a
	 */
Packit ae9e2a
Packit ae9e2a
	item_key = ((char *)item) + sc->item_path_offset;
Packit ae9e2a
	memcpy(item_key, key, keylen);
Packit ae9e2a
Packit ae9e2a
	pos = git_strmap_put(sc->map, item_key, &error);
Packit ae9e2a
	if (error < 0)
Packit ae9e2a
		goto done;
Packit ae9e2a
Packit ae9e2a
	if (!error)
Packit ae9e2a
		git_strmap_set_key_at(sc->map, pos, item_key);
Packit ae9e2a
	git_strmap_set_value_at(sc->map, pos, item);
Packit ae9e2a
Packit ae9e2a
	error = git_vector_insert(&sc->items, item);
Packit ae9e2a
	if (error < 0)
Packit ae9e2a
		git_strmap_delete_at(sc->map, pos);
Packit ae9e2a
Packit ae9e2a
done:
Packit ae9e2a
	if (out)
Packit ae9e2a
		*out = !error ? item : NULL;
Packit ae9e2a
	return error;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
/* lookup item by key */
Packit ae9e2a
void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key)
Packit ae9e2a
{
Packit ae9e2a
	khiter_t pos = git_strmap_lookup_index(sc->map, key);
Packit ae9e2a
	if (git_strmap_valid_index(sc->map, pos))
Packit ae9e2a
		return git_strmap_value_at(sc->map, pos);
Packit ae9e2a
	return NULL;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
/* find out how many items are in the cache */
Packit ae9e2a
size_t git_sortedcache_entrycount(const git_sortedcache *sc)
Packit ae9e2a
{
Packit ae9e2a
	return git_vector_length(&sc->items);
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
/* lookup item by index */
Packit ae9e2a
void *git_sortedcache_entry(git_sortedcache *sc, size_t pos)
Packit ae9e2a
{
Packit ae9e2a
	/* make sure the items are sorted so this gets the correct item */
Packit ae9e2a
	if (!git_vector_is_sorted(&sc->items))
Packit ae9e2a
		git_vector_sort(&sc->items);
Packit ae9e2a
Packit ae9e2a
	return git_vector_get(&sc->items, pos);
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
/* helper struct so bsearch callback can know offset + key value for cmp */
Packit ae9e2a
struct sortedcache_magic_key {
Packit ae9e2a
	size_t offset;
Packit ae9e2a
	const char *key;
Packit ae9e2a
};
Packit ae9e2a
Packit ae9e2a
static int sortedcache_magic_cmp(const void *key, const void *value)
Packit ae9e2a
{
Packit ae9e2a
	const struct sortedcache_magic_key *magic = key;
Packit ae9e2a
	const char *value_key = ((const char *)value) + magic->offset;
Packit ae9e2a
	return strcmp(magic->key, value_key);
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
/* lookup index of item by key */
Packit ae9e2a
int git_sortedcache_lookup_index(
Packit ae9e2a
	size_t *out, git_sortedcache *sc, const char *key)
Packit ae9e2a
{
Packit ae9e2a
	struct sortedcache_magic_key magic;
Packit ae9e2a
Packit ae9e2a
	magic.offset = sc->item_path_offset;
Packit ae9e2a
	magic.key    = key;
Packit ae9e2a
Packit ae9e2a
	return git_vector_bsearch2(out, &sc->items, sortedcache_magic_cmp, &magic);
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
/* remove entry from cache */
Packit ae9e2a
int git_sortedcache_remove(git_sortedcache *sc, size_t pos)
Packit ae9e2a
{
Packit ae9e2a
	char *item;
Packit ae9e2a
	khiter_t mappos;
Packit ae9e2a
Packit ae9e2a
	/* because of pool allocation, this can't actually remove the item,
Packit ae9e2a
	 * but we can remove it from the items vector and the hash table.
Packit ae9e2a
	 */
Packit ae9e2a
Packit ae9e2a
	if ((item = git_vector_get(&sc->items, pos)) == NULL) {
Packit ae9e2a
		giterr_set(GITERR_INVALID, "removing item out of range");
Packit ae9e2a
		return GIT_ENOTFOUND;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	(void)git_vector_remove(&sc->items, pos);
Packit ae9e2a
Packit ae9e2a
	mappos = git_strmap_lookup_index(sc->map, item + sc->item_path_offset);
Packit ae9e2a
	git_strmap_delete_at(sc->map, mappos);
Packit ae9e2a
Packit ae9e2a
	if (sc->free_item)
Packit ae9e2a
		sc->free_item(sc->free_item_payload, item);
Packit ae9e2a
Packit ae9e2a
	return 0;
Packit ae9e2a
}
Packit ae9e2a