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 if (git_pool_init(&sc
->pool
, 1) < 0 ||
29 git_vector_init(&sc
->items
, 4, item_cmp
) < 0 ||
30 git_strmap_new(&sc
->map
) < 0)
33 if (git_rwlock_init(&sc
->lock
)) {
34 git_error_set(GIT_ERROR_OS
, "failed to initialize lock");
38 sc
->item_path_offset
= item_path_offset
;
39 sc
->free_item
= free_item
;
40 sc
->free_item_payload
= free_item_payload
;
43 memcpy(sc
->path
, path
, pathlen
);
49 git_strmap_free(sc
->map
);
50 git_vector_free(&sc
->items
);
51 git_pool_clear(&sc
->pool
);
56 void git_sortedcache_incref(git_sortedcache
*sc
)
61 const char *git_sortedcache_path(git_sortedcache
*sc
)
66 static void sortedcache_clear(git_sortedcache
*sc
)
68 git_strmap_clear(sc
->map
);
74 git_vector_foreach(&sc
->items
, i
, item
) {
75 sc
->free_item(sc
->free_item_payload
, item
);
79 git_vector_clear(&sc
->items
);
81 git_pool_clear(&sc
->pool
);
84 static void sortedcache_free(git_sortedcache
*sc
)
86 /* acquire write lock to make sure everyone else is done */
87 if (git_sortedcache_wlock(sc
) < 0)
90 sortedcache_clear(sc
);
91 git_vector_free(&sc
->items
);
92 git_strmap_free(sc
->map
);
94 git_sortedcache_wunlock(sc
);
96 git_rwlock_free(&sc
->lock
);
100 void git_sortedcache_free(git_sortedcache
*sc
)
104 GIT_REFCOUNT_DEC(sc
, sortedcache_free
);
107 static int sortedcache_copy_item(void *payload
, void *tgt_item
, void *src_item
)
109 git_sortedcache
*sc
= payload
;
110 /* path will already have been copied by upsert */
111 memcpy(tgt_item
, src_item
, sc
->item_path_offset
);
115 /* copy a sorted cache */
116 int git_sortedcache_copy(
117 git_sortedcache
**out
,
118 git_sortedcache
*src
,
120 int (*copy_item
)(void *payload
, void *tgt_item
, void *src_item
),
124 git_sortedcache
*tgt
;
126 void *src_item
, *tgt_item
;
128 /* just use memcpy if no special copy fn is passed in */
130 copy_item
= sortedcache_copy_item
;
134 if ((error
= git_sortedcache_new(
135 &tgt
, src
->item_path_offset
,
136 src
->free_item
, src
->free_item_payload
,
137 src
->items
._cmp
, src
->path
)) < 0)
140 if (lock
&& git_sortedcache_rlock(src
) < 0) {
141 git_sortedcache_free(tgt
);
145 git_vector_foreach(&src
->items
, i
, src_item
) {
146 char *path
= ((char *)src_item
) + src
->item_path_offset
;
148 if ((error
= git_sortedcache_upsert(&tgt_item
, tgt
, path
)) < 0 ||
149 (error
= copy_item(payload
, tgt_item
, src_item
)) < 0)
154 git_sortedcache_runlock(src
);
156 git_sortedcache_free(tgt
);
158 *out
= !error
? tgt
: NULL
;
163 /* lock sortedcache while making modifications */
164 int git_sortedcache_wlock(git_sortedcache
*sc
)
166 GIT_UNUSED(sc
); /* prevent warning when compiled w/o threads */
168 if (git_rwlock_wrlock(&sc
->lock
) < 0) {
169 git_error_set(GIT_ERROR_OS
, "unable to acquire write lock on cache");
175 /* unlock sorted cache when done with modifications */
176 void git_sortedcache_wunlock(git_sortedcache
*sc
)
178 git_vector_sort(&sc
->items
);
179 git_rwlock_wrunlock(&sc
->lock
);
182 /* lock sortedcache for read */
183 int git_sortedcache_rlock(git_sortedcache
*sc
)
185 GIT_UNUSED(sc
); /* prevent warning when compiled w/o threads */
187 if (git_rwlock_rdlock(&sc
->lock
) < 0) {
188 git_error_set(GIT_ERROR_OS
, "unable to acquire read lock on cache");
194 /* unlock sorted cache when done reading */
195 void git_sortedcache_runlock(git_sortedcache
*sc
)
197 GIT_UNUSED(sc
); /* prevent warning when compiled w/o threads */
198 git_rwlock_rdunlock(&sc
->lock
);
201 /* if the file has changed, lock cache and load file contents into buf;
202 * returns <0 on error, >0 if file has not changed
204 int git_sortedcache_lockandload(git_sortedcache
*sc
, git_str
*buf
)
209 if ((error
= git_sortedcache_wlock(sc
)) < 0)
212 if ((error
= git_futils_filestamp_check(&sc
->stamp
, sc
->path
)) <= 0)
215 if ((fd
= git_futils_open_ro(sc
->path
)) < 0) {
220 if (p_fstat(fd
, &st
) < 0) {
221 git_error_set(GIT_ERROR_OS
, "failed to stat file");
227 if (!git__is_sizet(st
.st_size
)) {
228 git_error_set(GIT_ERROR_INVALID
, "unable to load file larger than size_t");
235 error
= git_futils_readbuffer_fd(buf
, fd
, (size_t)st
.st_size
);
242 return 1; /* return 1 -> file needs reload and was successfully loaded */
245 git_sortedcache_wunlock(sc
);
249 void git_sortedcache_updated(git_sortedcache
*sc
)
251 /* update filestamp to latest value */
252 git_futils_filestamp_check(&sc
->stamp
, sc
->path
);
255 /* release all items in sorted cache */
256 int git_sortedcache_clear(git_sortedcache
*sc
, bool wlock
)
258 if (wlock
&& git_sortedcache_wlock(sc
) < 0)
261 sortedcache_clear(sc
);
264 git_sortedcache_wunlock(sc
);
269 /* find and/or insert item, returning pointer to item data */
270 int git_sortedcache_upsert(void **out
, git_sortedcache
*sc
, const char *key
)
272 size_t keylen
, itemlen
;
277 if ((item
= git_strmap_get(sc
->map
, key
)) != NULL
)
280 keylen
= strlen(key
);
281 itemlen
= sc
->item_path_offset
+ keylen
+ 1;
282 itemlen
= (itemlen
+ 7) & ~7;
284 if ((item
= git_pool_mallocz(&sc
->pool
, itemlen
)) == NULL
) {
285 /* don't use GIT_ERROR_CHECK_ALLOC b/c of lock */
290 /* one strange thing is that even if the vector or hash table insert
291 * fail, there is no way to free the pool item so we just abandon it
294 item_key
= ((char *)item
) + sc
->item_path_offset
;
295 memcpy(item_key
, key
, keylen
);
297 if ((error
= git_strmap_set(sc
->map
, item_key
, item
)) < 0)
300 if ((error
= git_vector_insert(&sc
->items
, item
)) < 0)
301 git_strmap_delete(sc
->map
, item_key
);
305 *out
= !error
? item
: NULL
;
309 /* lookup item by key */
310 void *git_sortedcache_lookup(const git_sortedcache
*sc
, const char *key
)
312 return git_strmap_get(sc
->map
, key
);
315 /* find out how many items are in the cache */
316 size_t git_sortedcache_entrycount(const git_sortedcache
*sc
)
318 return git_vector_length(&sc
->items
);
321 /* lookup item by index */
322 void *git_sortedcache_entry(git_sortedcache
*sc
, size_t pos
)
324 /* make sure the items are sorted so this gets the correct item */
325 if (!git_vector_is_sorted(&sc
->items
))
326 git_vector_sort(&sc
->items
);
328 return git_vector_get(&sc
->items
, pos
);
331 /* helper struct so bsearch callback can know offset + key value for cmp */
332 struct sortedcache_magic_key
{
337 static int sortedcache_magic_cmp(const void *key
, const void *value
)
339 const struct sortedcache_magic_key
*magic
= key
;
340 const char *value_key
= ((const char *)value
) + magic
->offset
;
341 return strcmp(magic
->key
, value_key
);
344 /* lookup index of item by key */
345 int git_sortedcache_lookup_index(
346 size_t *out
, git_sortedcache
*sc
, const char *key
)
348 struct sortedcache_magic_key magic
;
350 magic
.offset
= sc
->item_path_offset
;
353 return git_vector_bsearch2(out
, &sc
->items
, sortedcache_magic_cmp
, &magic
);
356 /* remove entry from cache */
357 int git_sortedcache_remove(git_sortedcache
*sc
, size_t pos
)
362 * Because of pool allocation, this can't actually remove the item,
363 * but we can remove it from the items vector and the hash table.
366 if ((item
= git_vector_get(&sc
->items
, pos
)) == NULL
) {
367 git_error_set(GIT_ERROR_INVALID
, "removing item out of range");
368 return GIT_ENOTFOUND
;
371 (void)git_vector_remove(&sc
->items
, pos
);
373 git_strmap_delete(sc
->map
, item
+ sc
->item_path_offset
);
376 sc
->free_item(sc
->free_item_payload
, item
);