2 * Copyright (C) the libgit2 contributors. All rights reserved.
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
8 #include "sortedcache.h"
10 int git_sortedcache_new(
11 git_sortedcache
**out
,
12 size_t item_path_offset
,
13 git_sortedcache_free_item_fn free_item
,
14 void *free_item_payload
,
15 git_vector_cmp item_cmp
,
19 size_t pathlen
, alloclen
;
21 pathlen
= path
? strlen(path
) : 0;
23 GIT_ERROR_CHECK_ALLOC_ADD(&alloclen
, sizeof(git_sortedcache
), pathlen
);
24 GIT_ERROR_CHECK_ALLOC_ADD(&alloclen
, alloclen
, 1);
25 sc
= git__calloc(1, alloclen
);
26 GIT_ERROR_CHECK_ALLOC(sc
);
28 git_pool_init(&sc
->pool
, 1);
30 if (git_vector_init(&sc
->items
, 4, item_cmp
) < 0 ||
31 git_strmap_new(&sc
->map
) < 0)
34 if (git_rwlock_init(&sc
->lock
)) {
35 git_error_set(GIT_ERROR_OS
, "failed to initialize lock");
39 sc
->item_path_offset
= item_path_offset
;
40 sc
->free_item
= free_item
;
41 sc
->free_item_payload
= free_item_payload
;
44 memcpy(sc
->path
, path
, pathlen
);
50 git_strmap_free(sc
->map
);
51 git_vector_free(&sc
->items
);
52 git_pool_clear(&sc
->pool
);
57 void git_sortedcache_incref(git_sortedcache
*sc
)
62 const char *git_sortedcache_path(git_sortedcache
*sc
)
67 static void sortedcache_clear(git_sortedcache
*sc
)
69 git_strmap_clear(sc
->map
);
75 git_vector_foreach(&sc
->items
, i
, item
) {
76 sc
->free_item(sc
->free_item_payload
, item
);
80 git_vector_clear(&sc
->items
);
82 git_pool_clear(&sc
->pool
);
85 static void sortedcache_free(git_sortedcache
*sc
)
87 /* acquire write lock to make sure everyone else is done */
88 if (git_sortedcache_wlock(sc
) < 0)
91 sortedcache_clear(sc
);
92 git_vector_free(&sc
->items
);
93 git_strmap_free(sc
->map
);
95 git_sortedcache_wunlock(sc
);
97 git_rwlock_free(&sc
->lock
);
101 void git_sortedcache_free(git_sortedcache
*sc
)
105 GIT_REFCOUNT_DEC(sc
, sortedcache_free
);
108 static int sortedcache_copy_item(void *payload
, void *tgt_item
, void *src_item
)
110 git_sortedcache
*sc
= payload
;
111 /* path will already have been copied by upsert */
112 memcpy(tgt_item
, src_item
, sc
->item_path_offset
);
116 /* copy a sorted cache */
117 int git_sortedcache_copy(
118 git_sortedcache
**out
,
119 git_sortedcache
*src
,
121 int (*copy_item
)(void *payload
, void *tgt_item
, void *src_item
),
125 git_sortedcache
*tgt
;
127 void *src_item
, *tgt_item
;
129 /* just use memcpy if no special copy fn is passed in */
131 copy_item
= sortedcache_copy_item
;
135 if ((error
= git_sortedcache_new(
136 &tgt
, src
->item_path_offset
,
137 src
->free_item
, src
->free_item_payload
,
138 src
->items
._cmp
, src
->path
)) < 0)
141 if (lock
&& git_sortedcache_rlock(src
) < 0) {
142 git_sortedcache_free(tgt
);
146 git_vector_foreach(&src
->items
, i
, src_item
) {
147 char *path
= ((char *)src_item
) + src
->item_path_offset
;
149 if ((error
= git_sortedcache_upsert(&tgt_item
, tgt
, path
)) < 0 ||
150 (error
= copy_item(payload
, tgt_item
, src_item
)) < 0)
155 git_sortedcache_runlock(src
);
157 git_sortedcache_free(tgt
);
159 *out
= !error
? tgt
: NULL
;
164 /* lock sortedcache while making modifications */
165 int git_sortedcache_wlock(git_sortedcache
*sc
)
167 GIT_UNUSED(sc
); /* prevent warning when compiled w/o threads */
169 if (git_rwlock_wrlock(&sc
->lock
) < 0) {
170 git_error_set(GIT_ERROR_OS
, "unable to acquire write lock on cache");
176 /* unlock sorted cache when done with modifications */
177 void git_sortedcache_wunlock(git_sortedcache
*sc
)
179 git_vector_sort(&sc
->items
);
180 git_rwlock_wrunlock(&sc
->lock
);
183 /* lock sortedcache for read */
184 int git_sortedcache_rlock(git_sortedcache
*sc
)
186 GIT_UNUSED(sc
); /* prevent warning when compiled w/o threads */
188 if (git_rwlock_rdlock(&sc
->lock
) < 0) {
189 git_error_set(GIT_ERROR_OS
, "unable to acquire read lock on cache");
195 /* unlock sorted cache when done reading */
196 void git_sortedcache_runlock(git_sortedcache
*sc
)
198 GIT_UNUSED(sc
); /* prevent warning when compiled w/o threads */
199 git_rwlock_rdunlock(&sc
->lock
);
202 /* if the file has changed, lock cache and load file contents into buf;
203 * returns <0 on error, >0 if file has not changed
205 int git_sortedcache_lockandload(git_sortedcache
*sc
, git_buf
*buf
)
210 if ((error
= git_sortedcache_wlock(sc
)) < 0)
213 if ((error
= git_futils_filestamp_check(&sc
->stamp
, sc
->path
)) <= 0)
216 if ((fd
= git_futils_open_ro(sc
->path
)) < 0) {
221 if (p_fstat(fd
, &st
) < 0) {
222 git_error_set(GIT_ERROR_OS
, "failed to stat file");
228 if (!git__is_sizet(st
.st_size
)) {
229 git_error_set(GIT_ERROR_INVALID
, "unable to load file larger than size_t");
236 error
= git_futils_readbuffer_fd(buf
, fd
, (size_t)st
.st_size
);
243 return 1; /* return 1 -> file needs reload and was successfully loaded */
246 git_sortedcache_wunlock(sc
);
250 void git_sortedcache_updated(git_sortedcache
*sc
)
252 /* update filestamp to latest value */
253 git_futils_filestamp_check(&sc
->stamp
, sc
->path
);
256 /* release all items in sorted cache */
257 int git_sortedcache_clear(git_sortedcache
*sc
, bool wlock
)
259 if (wlock
&& git_sortedcache_wlock(sc
) < 0)
262 sortedcache_clear(sc
);
265 git_sortedcache_wunlock(sc
);
270 /* find and/or insert item, returning pointer to item data */
271 int git_sortedcache_upsert(void **out
, git_sortedcache
*sc
, const char *key
)
273 size_t keylen
, itemlen
;
278 if ((item
= git_strmap_get(sc
->map
, key
)) != NULL
)
281 keylen
= strlen(key
);
282 itemlen
= sc
->item_path_offset
+ keylen
+ 1;
283 itemlen
= (itemlen
+ 7) & ~7;
285 if ((item
= git_pool_mallocz(&sc
->pool
, itemlen
)) == NULL
) {
286 /* don't use GIT_ERROR_CHECK_ALLOC b/c of lock */
291 /* one strange thing is that even if the vector or hash table insert
292 * fail, there is no way to free the pool item so we just abandon it
295 item_key
= ((char *)item
) + sc
->item_path_offset
;
296 memcpy(item_key
, key
, keylen
);
298 if ((error
= git_strmap_set(sc
->map
, item_key
, item
)) < 0)
301 if ((error
= git_vector_insert(&sc
->items
, item
)) < 0)
302 git_strmap_delete(sc
->map
, item_key
);
306 *out
= !error
? item
: NULL
;
310 /* lookup item by key */
311 void *git_sortedcache_lookup(const git_sortedcache
*sc
, const char *key
)
313 return git_strmap_get(sc
->map
, key
);
316 /* find out how many items are in the cache */
317 size_t git_sortedcache_entrycount(const git_sortedcache
*sc
)
319 return git_vector_length(&sc
->items
);
322 /* lookup item by index */
323 void *git_sortedcache_entry(git_sortedcache
*sc
, size_t pos
)
325 /* make sure the items are sorted so this gets the correct item */
326 if (!git_vector_is_sorted(&sc
->items
))
327 git_vector_sort(&sc
->items
);
329 return git_vector_get(&sc
->items
, pos
);
332 /* helper struct so bsearch callback can know offset + key value for cmp */
333 struct sortedcache_magic_key
{
338 static int sortedcache_magic_cmp(const void *key
, const void *value
)
340 const struct sortedcache_magic_key
*magic
= key
;
341 const char *value_key
= ((const char *)value
) + magic
->offset
;
342 return strcmp(magic
->key
, value_key
);
345 /* lookup index of item by key */
346 int git_sortedcache_lookup_index(
347 size_t *out
, git_sortedcache
*sc
, const char *key
)
349 struct sortedcache_magic_key magic
;
351 magic
.offset
= sc
->item_path_offset
;
354 return git_vector_bsearch2(out
, &sc
->items
, sortedcache_magic_cmp
, &magic
);
357 /* remove entry from cache */
358 int git_sortedcache_remove(git_sortedcache
*sc
, size_t pos
)
363 * Because of pool allocation, this can't actually remove the item,
364 * but we can remove it from the items vector and the hash table.
367 if ((item
= git_vector_get(&sc
->items
, pos
)) == NULL
) {
368 git_error_set(GIT_ERROR_INVALID
, "removing item out of range");
369 return GIT_ENOTFOUND
;
372 (void)git_vector_remove(&sc
->items
, pos
);
374 git_strmap_delete(sc
->map
, item
+ sc
->item_path_offset
);
377 sc
->free_item(sc
->free_item_payload
, item
);